mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-11 15:12:47 +09:00
Add mouse support
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/cmd/micro/buffer"
|
||||
"github.com/zyedidia/micro/cmd/micro/config"
|
||||
"github.com/zyedidia/micro/cmd/micro/screen"
|
||||
"github.com/zyedidia/micro/cmd/micro/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
@@ -34,6 +35,57 @@ func (h *BufHandler) ScrollDown(n int) {
|
||||
// MousePress is the event that should happen when a normal click happens
|
||||
// This is almost always bound to left click
|
||||
func (h *BufHandler) MousePress(e *tcell.EventMouse) bool {
|
||||
b := h.Buf
|
||||
mx, my := e.Position()
|
||||
mouseLoc := h.Win.GetMouseLoc(buffer.Loc{mx, my})
|
||||
h.Cursor.Loc = mouseLoc
|
||||
if h.mouseReleased {
|
||||
if b.NumCursors() > 1 {
|
||||
b.ClearCursors()
|
||||
h.Win.Relocate()
|
||||
}
|
||||
if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
|
||||
if h.doubleClick {
|
||||
// Triple click
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.tripleClick = true
|
||||
h.doubleClick = false
|
||||
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection("primary")
|
||||
} else {
|
||||
// Double click
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.doubleClick = true
|
||||
h.tripleClick = false
|
||||
|
||||
h.Cursor.SelectWord()
|
||||
h.Cursor.CopySelection("primary")
|
||||
}
|
||||
} else {
|
||||
h.doubleClick = false
|
||||
h.tripleClick = false
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
h.Cursor.CurSelection[0] = h.Cursor.Loc
|
||||
h.Cursor.CurSelection[1] = h.Cursor.Loc
|
||||
}
|
||||
h.mouseReleased = false
|
||||
} else if !h.mouseReleased {
|
||||
if h.tripleClick {
|
||||
h.Cursor.AddLineToSelection()
|
||||
} else if h.doubleClick {
|
||||
h.Cursor.AddWordToSelection()
|
||||
} else {
|
||||
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
h.Cursor.CopySelection("primary")
|
||||
}
|
||||
}
|
||||
|
||||
h.lastLoc = mouseLoc
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ func NewBufHandler(buf *buffer.Buffer, win display.Window) *BufHandler {
|
||||
|
||||
h.cursors = []*buffer.Cursor{buffer.NewCursor(buf, buf.StartCursor)}
|
||||
h.Cursor = h.cursors[0]
|
||||
h.mouseReleased = true
|
||||
|
||||
buf.SetCursors(h.cursors)
|
||||
return h
|
||||
@@ -105,16 +106,51 @@ func (h *BufHandler) HandleEvent(event tcell.Event) {
|
||||
mod: e.Modifiers(),
|
||||
r: e.Rune(),
|
||||
}
|
||||
done := h.DoKeyEvent(ke)
|
||||
if !done && e.Key() == tcell.KeyRune {
|
||||
h.DoRuneInsert(e.Rune())
|
||||
cursors := h.Buf.GetCursors()
|
||||
for _, c := range cursors {
|
||||
h.Buf.SetCurCursor(c.Num)
|
||||
h.Cursor = c
|
||||
done := h.DoKeyEvent(ke)
|
||||
if !done && e.Key() == tcell.KeyRune {
|
||||
h.DoRuneInsert(e.Rune())
|
||||
}
|
||||
}
|
||||
// TODO: maybe reset curcursor to 0
|
||||
case *tcell.EventMouse:
|
||||
switch e.Buttons() {
|
||||
case tcell.ButtonNone:
|
||||
// Mouse event with no click
|
||||
if !h.mouseReleased {
|
||||
// Mouse was just released
|
||||
|
||||
mx, my := e.Position()
|
||||
mouseLoc := h.Win.GetMouseLoc(buffer.Loc{X: mx, Y: my})
|
||||
|
||||
// Relocating here isn't really necessary because the cursor will
|
||||
// be in the right place from the last mouse event
|
||||
// However, if we are running in a terminal that doesn't support mouse motion
|
||||
// events, this still allows the user to make selections, except only after they
|
||||
// release the mouse
|
||||
|
||||
if !h.doubleClick && !h.tripleClick {
|
||||
h.Cursor.Loc = mouseLoc
|
||||
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
h.Cursor.CopySelection("primary")
|
||||
}
|
||||
h.mouseReleased = true
|
||||
}
|
||||
}
|
||||
|
||||
me := MouseEvent{
|
||||
btn: e.Buttons(),
|
||||
mod: e.Modifiers(),
|
||||
}
|
||||
h.DoMouseEvent(me, e)
|
||||
cursors := h.Buf.GetCursors()
|
||||
for _, c := range cursors {
|
||||
h.Buf.SetCurCursor(c.Num)
|
||||
h.Cursor = c
|
||||
h.DoMouseEvent(me, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ type Buffer struct {
|
||||
*EventHandler
|
||||
|
||||
cursors []*Cursor
|
||||
curCursor int
|
||||
StartCursor Loc
|
||||
|
||||
// Path to the file on disk
|
||||
@@ -229,9 +230,10 @@ func (b *Buffer) ReOpen() error {
|
||||
|
||||
b.ModTime, err = GetModTime(b.Path)
|
||||
b.isModified = false
|
||||
for _, c := range b.cursors {
|
||||
c.Relocate()
|
||||
}
|
||||
return err
|
||||
// TODO: buffer cursor
|
||||
// b.Cursor.Relocate()
|
||||
}
|
||||
|
||||
// SetCursors resets this buffer's cursors to a new list
|
||||
@@ -239,9 +241,14 @@ func (b *Buffer) SetCursors(c []*Cursor) {
|
||||
b.cursors = c
|
||||
}
|
||||
|
||||
// SetCurCursor sets the current cursor
|
||||
func (b *Buffer) SetCurCursor(n int) {
|
||||
b.curCursor = n
|
||||
}
|
||||
|
||||
// GetActiveCursor returns the main cursor in this buffer
|
||||
func (b *Buffer) GetActiveCursor() *Cursor {
|
||||
return b.cursors[0]
|
||||
return b.cursors[b.curCursor]
|
||||
}
|
||||
|
||||
// GetCursor returns the nth cursor
|
||||
@@ -421,3 +428,49 @@ func (b *Buffer) IndentString(tabsize int) string {
|
||||
}
|
||||
return "\t"
|
||||
}
|
||||
|
||||
// MergeCursors merges any cursors that are at the same position
|
||||
// into one cursor
|
||||
func (b *Buffer) MergeCursors() {
|
||||
var cursors []*Cursor
|
||||
for i := 0; i < len(b.cursors); i++ {
|
||||
c1 := b.cursors[i]
|
||||
if c1 != nil {
|
||||
for j := 0; j < len(b.cursors); j++ {
|
||||
c2 := b.cursors[j]
|
||||
if c2 != nil && i != j && c1.Loc == c2.Loc {
|
||||
b.cursors[j] = nil
|
||||
}
|
||||
}
|
||||
cursors = append(cursors, c1)
|
||||
}
|
||||
}
|
||||
|
||||
b.cursors = cursors
|
||||
|
||||
for i := range b.cursors {
|
||||
b.cursors[i].Num = i
|
||||
}
|
||||
|
||||
if b.curCursor >= len(b.cursors) {
|
||||
b.curCursor = len(b.cursors) - 1
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateCursors updates all the cursors indicies
|
||||
func (b *Buffer) UpdateCursors() {
|
||||
for i, c := range b.cursors {
|
||||
c.Num = i
|
||||
}
|
||||
}
|
||||
|
||||
// ClearCursors removes all extra cursors
|
||||
func (b *Buffer) ClearCursors() {
|
||||
for i := 1; i < len(b.cursors); i++ {
|
||||
b.cursors[i] = nil
|
||||
}
|
||||
b.cursors = b.cursors[:1]
|
||||
b.UpdateCursors()
|
||||
b.curCursor = 0
|
||||
b.GetActiveCursor().ResetSelection()
|
||||
}
|
||||
|
||||
6
cmd/micro/config/globals.go
Normal file
6
cmd/micro/config/globals.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
const (
|
||||
DoubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
|
||||
AutosaveTime = 8 // Number of seconds to wait before autosaving
|
||||
)
|
||||
@@ -81,6 +81,13 @@ func (i *InfoWindow) Relocate() bool { return false }
|
||||
func (i *InfoWindow) GetView() *View { return i.View }
|
||||
func (i *InfoWindow) SetView(v *View) {}
|
||||
|
||||
func (i *InfoWindow) GetMouseLoc(vloc buffer.Loc) buffer.Loc {
|
||||
c := i.Buffer.GetActiveCursor()
|
||||
l := i.Buffer.LineBytes(0)
|
||||
n := utf8.RuneCountInString(i.Msg)
|
||||
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
|
||||
}
|
||||
|
||||
func (i *InfoWindow) Clear() {
|
||||
for x := 0; x < i.width; x++ {
|
||||
screen.Screen.SetContent(x, i.y, ' ', nil, config.DefStyle)
|
||||
|
||||
@@ -24,6 +24,7 @@ type Window interface {
|
||||
Relocate() bool
|
||||
GetView() *View
|
||||
SetView(v *View)
|
||||
GetMouseLoc(vloc buffer.Loc) buffer.Loc
|
||||
}
|
||||
|
||||
// The BufWindow provides a way of displaying a certain section
|
||||
@@ -75,6 +76,7 @@ func (w *BufWindow) Clear() {
|
||||
func (w *BufWindow) Bottomline() int {
|
||||
// b := w.Buf
|
||||
|
||||
// TODO: possible non-softwrap optimization
|
||||
// if !b.Settings["softwrap"].(bool) {
|
||||
// return w.StartLine + w.Height
|
||||
// }
|
||||
@@ -130,6 +132,118 @@ func (w *BufWindow) Relocate() bool {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *BufWindow) GetMouseLoc(svloc buffer.Loc) buffer.Loc {
|
||||
b := w.Buf
|
||||
|
||||
// TODO: possible non-softwrap optimization
|
||||
// if !b.Settings["softwrap"].(bool) {
|
||||
// l := b.LineBytes(svloc.Y)
|
||||
// return buffer.Loc{b.GetActiveCursor().GetCharPosInLine(l, svloc.X), svloc.Y}
|
||||
// }
|
||||
|
||||
bufHeight := w.Height
|
||||
if b.Settings["statusline"].(bool) {
|
||||
bufHeight--
|
||||
}
|
||||
|
||||
// We need to know the string length of the largest line number
|
||||
// so we can pad appropriately when displaying line numbers
|
||||
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
|
||||
|
||||
tabsize := int(b.Settings["tabsize"].(float64))
|
||||
softwrap := b.Settings["softwrap"].(bool)
|
||||
|
||||
// this represents the current draw position
|
||||
// within the current window
|
||||
vloc := buffer.Loc{X: 0, Y: 0}
|
||||
|
||||
// this represents the current draw position in the buffer (char positions)
|
||||
bloc := buffer.Loc{X: w.StartCol, Y: w.StartLine}
|
||||
|
||||
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
|
||||
vloc.X = 0
|
||||
if b.Settings["ruler"].(bool) {
|
||||
vloc.X += maxLineNumLength + 1
|
||||
}
|
||||
|
||||
if svloc.X <= vloc.X && vloc.Y == svloc.Y {
|
||||
return bloc
|
||||
}
|
||||
|
||||
line := b.LineBytes(bloc.Y)
|
||||
line, nColsBeforeStart := util.SliceVisualEnd(line, bloc.X, tabsize)
|
||||
|
||||
draw := func() {
|
||||
if nColsBeforeStart <= 0 {
|
||||
vloc.X++
|
||||
}
|
||||
nColsBeforeStart--
|
||||
}
|
||||
|
||||
w.lineHeight[vloc.Y] = bloc.Y
|
||||
|
||||
totalwidth := bloc.X - nColsBeforeStart
|
||||
for len(line) > 0 {
|
||||
if vloc.X == svloc.X && vloc.Y == svloc.Y {
|
||||
return bloc
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRune(line)
|
||||
draw()
|
||||
width := 0
|
||||
|
||||
switch r {
|
||||
case '\t':
|
||||
ts := tabsize - (totalwidth % tabsize)
|
||||
width = ts
|
||||
default:
|
||||
width = runewidth.RuneWidth(r)
|
||||
}
|
||||
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if width > 1 {
|
||||
for i := 1; i < width; i++ {
|
||||
if vloc.X == svloc.X && vloc.Y == svloc.Y {
|
||||
return bloc
|
||||
}
|
||||
draw()
|
||||
}
|
||||
}
|
||||
bloc.X++
|
||||
line = line[size:]
|
||||
|
||||
totalwidth += width
|
||||
|
||||
// If we reach the end of the window then we either stop or we wrap for softwrap
|
||||
if vloc.X >= w.Width {
|
||||
if !softwrap {
|
||||
break
|
||||
} else {
|
||||
vloc.Y++
|
||||
if vloc.Y >= bufHeight {
|
||||
break
|
||||
}
|
||||
vloc.X = 0
|
||||
w.lineHeight[vloc.Y] = bloc.Y
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
vloc.X += maxLineNumLength + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if vloc.Y == svloc.Y {
|
||||
return bloc
|
||||
}
|
||||
|
||||
bloc.X = w.StartCol
|
||||
bloc.Y++
|
||||
if bloc.Y >= b.LinesNum() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.Loc{X: -1, Y: -1}
|
||||
}
|
||||
|
||||
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
lineNum := strconv.Itoa(bloc.Y + 1)
|
||||
|
||||
|
||||
@@ -17,11 +17,6 @@ import (
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
const (
|
||||
doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
|
||||
autosaveTime = 8 // Number of seconds to wait before autosaving
|
||||
)
|
||||
|
||||
var (
|
||||
// These variables should be set by the linker when compiling
|
||||
|
||||
|
||||
Reference in New Issue
Block a user