forked from toolshed/abra
		
	
		
			
				
	
	
		
			309 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			309 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package tea
 | |
| 
 | |
| import "strconv"
 | |
| 
 | |
| // MouseMsg contains information about a mouse event and are sent to a programs
 | |
| // update function when mouse activity occurs. Note that the mouse must first
 | |
| // be enabled in order for the mouse events to be received.
 | |
| type MouseMsg MouseEvent
 | |
| 
 | |
| // String returns a string representation of a mouse event.
 | |
| func (m MouseMsg) String() string {
 | |
| 	return MouseEvent(m).String()
 | |
| }
 | |
| 
 | |
| // MouseEvent represents a mouse event, which could be a click, a scroll wheel
 | |
| // movement, a cursor movement, or a combination.
 | |
| type MouseEvent struct {
 | |
| 	X      int
 | |
| 	Y      int
 | |
| 	Shift  bool
 | |
| 	Alt    bool
 | |
| 	Ctrl   bool
 | |
| 	Action MouseAction
 | |
| 	Button MouseButton
 | |
| 
 | |
| 	// Deprecated: Use MouseAction & MouseButton instead.
 | |
| 	Type MouseEventType
 | |
| }
 | |
| 
 | |
| // IsWheel returns true if the mouse event is a wheel event.
 | |
| func (m MouseEvent) IsWheel() bool {
 | |
| 	return m.Button == MouseButtonWheelUp || m.Button == MouseButtonWheelDown ||
 | |
| 		m.Button == MouseButtonWheelLeft || m.Button == MouseButtonWheelRight
 | |
| }
 | |
| 
 | |
| // String returns a string representation of a mouse event.
 | |
| func (m MouseEvent) String() (s string) {
 | |
| 	if m.Ctrl {
 | |
| 		s += "ctrl+"
 | |
| 	}
 | |
| 	if m.Alt {
 | |
| 		s += "alt+"
 | |
| 	}
 | |
| 	if m.Shift {
 | |
| 		s += "shift+"
 | |
| 	}
 | |
| 
 | |
| 	if m.Button == MouseButtonNone { //nolint:nestif
 | |
| 		if m.Action == MouseActionMotion || m.Action == MouseActionRelease {
 | |
| 			s += mouseActions[m.Action]
 | |
| 		} else {
 | |
| 			s += "unknown"
 | |
| 		}
 | |
| 	} else if m.IsWheel() {
 | |
| 		s += mouseButtons[m.Button]
 | |
| 	} else {
 | |
| 		btn := mouseButtons[m.Button]
 | |
| 		if btn != "" {
 | |
| 			s += btn
 | |
| 		}
 | |
| 		act := mouseActions[m.Action]
 | |
| 		if act != "" {
 | |
| 			s += " " + act
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return s
 | |
| }
 | |
| 
 | |
| // MouseAction represents the action that occurred during a mouse event.
 | |
| type MouseAction int
 | |
| 
 | |
| // Mouse event actions.
 | |
| const (
 | |
| 	MouseActionPress MouseAction = iota
 | |
| 	MouseActionRelease
 | |
| 	MouseActionMotion
 | |
| )
 | |
| 
 | |
| var mouseActions = map[MouseAction]string{
 | |
| 	MouseActionPress:   "press",
 | |
| 	MouseActionRelease: "release",
 | |
| 	MouseActionMotion:  "motion",
 | |
| }
 | |
| 
 | |
| // MouseButton represents the button that was pressed during a mouse event.
 | |
| type MouseButton int
 | |
| 
 | |
| // Mouse event buttons
 | |
| //
 | |
| // This is based on X11 mouse button codes.
 | |
| //
 | |
| //	1 = left button
 | |
| //	2 = middle button (pressing the scroll wheel)
 | |
| //	3 = right button
 | |
| //	4 = turn scroll wheel up
 | |
| //	5 = turn scroll wheel down
 | |
| //	6 = push scroll wheel left
 | |
| //	7 = push scroll wheel right
 | |
| //	8 = 4th button (aka browser backward button)
 | |
| //	9 = 5th button (aka browser forward button)
 | |
| //	10
 | |
| //	11
 | |
| //
 | |
| // Other buttons are not supported.
 | |
| const (
 | |
| 	MouseButtonNone MouseButton = iota
 | |
| 	MouseButtonLeft
 | |
| 	MouseButtonMiddle
 | |
| 	MouseButtonRight
 | |
| 	MouseButtonWheelUp
 | |
| 	MouseButtonWheelDown
 | |
| 	MouseButtonWheelLeft
 | |
| 	MouseButtonWheelRight
 | |
| 	MouseButtonBackward
 | |
| 	MouseButtonForward
 | |
| 	MouseButton10
 | |
| 	MouseButton11
 | |
| )
 | |
| 
 | |
| var mouseButtons = map[MouseButton]string{
 | |
| 	MouseButtonNone:       "none",
 | |
| 	MouseButtonLeft:       "left",
 | |
| 	MouseButtonMiddle:     "middle",
 | |
| 	MouseButtonRight:      "right",
 | |
| 	MouseButtonWheelUp:    "wheel up",
 | |
| 	MouseButtonWheelDown:  "wheel down",
 | |
| 	MouseButtonWheelLeft:  "wheel left",
 | |
| 	MouseButtonWheelRight: "wheel right",
 | |
| 	MouseButtonBackward:   "backward",
 | |
| 	MouseButtonForward:    "forward",
 | |
| 	MouseButton10:         "button 10",
 | |
| 	MouseButton11:         "button 11",
 | |
| }
 | |
| 
 | |
| // MouseEventType indicates the type of mouse event occurring.
 | |
| //
 | |
| // Deprecated: Use MouseAction & MouseButton instead.
 | |
| type MouseEventType int
 | |
| 
 | |
| // Mouse event types.
 | |
| //
 | |
| // Deprecated: Use MouseAction & MouseButton instead.
 | |
| const (
 | |
| 	MouseUnknown MouseEventType = iota
 | |
| 	MouseLeft
 | |
| 	MouseRight
 | |
| 	MouseMiddle
 | |
| 	MouseRelease // mouse button release (X10 only)
 | |
| 	MouseWheelUp
 | |
| 	MouseWheelDown
 | |
| 	MouseWheelLeft
 | |
| 	MouseWheelRight
 | |
| 	MouseBackward
 | |
| 	MouseForward
 | |
| 	MouseMotion
 | |
| )
 | |
| 
 | |
| // Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events
 | |
| // look like:
 | |
| //
 | |
| //	ESC [ < Cb ; Cx ; Cy (M or m)
 | |
| //
 | |
| // where:
 | |
| //
 | |
| //	Cb is the encoded button code
 | |
| //	Cx is the x-coordinate of the mouse
 | |
| //	Cy is the y-coordinate of the mouse
 | |
| //	M is for button press, m is for button release
 | |
| //
 | |
| // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
 | |
| func parseSGRMouseEvent(buf []byte) MouseEvent {
 | |
| 	str := string(buf[3:])
 | |
| 	matches := mouseSGRRegex.FindStringSubmatch(str)
 | |
| 	if len(matches) != 5 { //nolint:mnd
 | |
| 		// Unreachable, we already checked the regex in `detectOneMsg`.
 | |
| 		panic("invalid mouse event")
 | |
| 	}
 | |
| 
 | |
| 	b, _ := strconv.Atoi(matches[1])
 | |
| 	px := matches[2]
 | |
| 	py := matches[3]
 | |
| 	release := matches[4] == "m"
 | |
| 	m := parseMouseButton(b, true)
 | |
| 
 | |
| 	// Wheel buttons don't have release events
 | |
| 	// Motion can be reported as a release event in some terminals (Windows Terminal)
 | |
| 	if m.Action != MouseActionMotion && !m.IsWheel() && release {
 | |
| 		m.Action = MouseActionRelease
 | |
| 		m.Type = MouseRelease
 | |
| 	}
 | |
| 
 | |
| 	x, _ := strconv.Atoi(px)
 | |
| 	y, _ := strconv.Atoi(py)
 | |
| 
 | |
| 	// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
 | |
| 	m.X = x - 1
 | |
| 	m.Y = y - 1
 | |
| 
 | |
| 	return m
 | |
| }
 | |
| 
 | |
| const x10MouseByteOffset = 32
 | |
| 
 | |
| // Parse X10-encoded mouse events; the simplest kind. The last release of X10
 | |
| // was December 1986, by the way. The original X10 mouse protocol limits the Cx
 | |
| // and Cy coordinates to 223 (=255-032).
 | |
| //
 | |
| // X10 mouse events look like:
 | |
| //
 | |
| //	ESC [M Cb Cx Cy
 | |
| //
 | |
| // See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
 | |
| func parseX10MouseEvent(buf []byte) MouseEvent {
 | |
| 	v := buf[3:6]
 | |
| 	m := parseMouseButton(int(v[0]), false)
 | |
| 
 | |
| 	// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
 | |
| 	m.X = int(v[1]) - x10MouseByteOffset - 1
 | |
| 	m.Y = int(v[2]) - x10MouseByteOffset - 1
 | |
| 
 | |
| 	return m
 | |
| }
 | |
| 
 | |
| // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
 | |
| func parseMouseButton(b int, isSGR bool) MouseEvent {
 | |
| 	var m MouseEvent
 | |
| 	e := b
 | |
| 	if !isSGR {
 | |
| 		e -= x10MouseByteOffset
 | |
| 	}
 | |
| 
 | |
| 	const (
 | |
| 		bitShift  = 0b0000_0100
 | |
| 		bitAlt    = 0b0000_1000
 | |
| 		bitCtrl   = 0b0001_0000
 | |
| 		bitMotion = 0b0010_0000
 | |
| 		bitWheel  = 0b0100_0000
 | |
| 		bitAdd    = 0b1000_0000 // additional buttons 8-11
 | |
| 
 | |
| 		bitsMask = 0b0000_0011
 | |
| 	)
 | |
| 
 | |
| 	if e&bitAdd != 0 {
 | |
| 		m.Button = MouseButtonBackward + MouseButton(e&bitsMask)
 | |
| 	} else if e&bitWheel != 0 {
 | |
| 		m.Button = MouseButtonWheelUp + MouseButton(e&bitsMask)
 | |
| 	} else {
 | |
| 		m.Button = MouseButtonLeft + MouseButton(e&bitsMask)
 | |
| 		// X10 reports a button release as 0b0000_0011 (3)
 | |
| 		if e&bitsMask == bitsMask {
 | |
| 			m.Action = MouseActionRelease
 | |
| 			m.Button = MouseButtonNone
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Motion bit doesn't get reported for wheel events.
 | |
| 	if e&bitMotion != 0 && !m.IsWheel() {
 | |
| 		m.Action = MouseActionMotion
 | |
| 	}
 | |
| 
 | |
| 	// Modifiers
 | |
| 	m.Alt = e&bitAlt != 0
 | |
| 	m.Ctrl = e&bitCtrl != 0
 | |
| 	m.Shift = e&bitShift != 0
 | |
| 
 | |
| 	// backward compatibility
 | |
| 	switch {
 | |
| 	case m.Button == MouseButtonLeft && m.Action == MouseActionPress:
 | |
| 		m.Type = MouseLeft
 | |
| 	case m.Button == MouseButtonMiddle && m.Action == MouseActionPress:
 | |
| 		m.Type = MouseMiddle
 | |
| 	case m.Button == MouseButtonRight && m.Action == MouseActionPress:
 | |
| 		m.Type = MouseRight
 | |
| 	case m.Button == MouseButtonNone && m.Action == MouseActionRelease:
 | |
| 		m.Type = MouseRelease
 | |
| 	case m.Button == MouseButtonWheelUp && m.Action == MouseActionPress:
 | |
| 		m.Type = MouseWheelUp
 | |
| 	case m.Button == MouseButtonWheelDown && m.Action == MouseActionPress:
 | |
| 		m.Type = MouseWheelDown
 | |
| 	case m.Button == MouseButtonWheelLeft && m.Action == MouseActionPress:
 | |
| 		m.Type = MouseWheelLeft
 | |
| 	case m.Button == MouseButtonWheelRight && m.Action == MouseActionPress:
 | |
| 		m.Type = MouseWheelRight
 | |
| 	case m.Button == MouseButtonBackward && m.Action == MouseActionPress:
 | |
| 		m.Type = MouseBackward
 | |
| 	case m.Button == MouseButtonForward && m.Action == MouseActionPress:
 | |
| 		m.Type = MouseForward
 | |
| 	case m.Action == MouseActionMotion:
 | |
| 		m.Type = MouseMotion
 | |
| 		switch m.Button { //nolint:exhaustive
 | |
| 		case MouseButtonLeft:
 | |
| 			m.Type = MouseLeft
 | |
| 		case MouseButtonMiddle:
 | |
| 			m.Type = MouseMiddle
 | |
| 		case MouseButtonRight:
 | |
| 			m.Type = MouseRight
 | |
| 		case MouseButtonBackward:
 | |
| 			m.Type = MouseBackward
 | |
| 		case MouseButtonForward:
 | |
| 			m.Type = MouseForward
 | |
| 		}
 | |
| 	default:
 | |
| 		m.Type = MouseUnknown
 | |
| 	}
 | |
| 
 | |
| 	return m
 | |
| }
 |