From 34ac83b5942673c6ae48f4ed0abec96bc60d4f49 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Tue, 25 Oct 2022 23:31:50 +0200 Subject: [PATCH 01/43] Introduce mouse release and mouse drag events Introduce separate mouse release and mouse drag (move while pressed) events: MouseLeftRelease, MouseLeftDrag, MouseRightRelease etc, to allow binding them to actions independently from mouse press events (MouseLeft, MouseRight etc). This change: - Makes it possible to handle mouse release and drag for arbitrary mouse events and actions (including Lua actions), not just for MouseLeft as in the current code. - Fixes issue #2599 with PastePrimary and MouseMultiCursor actions: selection is pasted not only when pressing MouseMiddle but also when moving mouse with MouseMiddle pressed; similarly, a new multicursor is added not only when pressing Ctrl-MouseLeft but also when moving mouse with Ctrl-MouseLeft pressed. My initial approach was not to introduce new events for mouse release and mouse drag but to pass "mouse released" info to action functions in addition to *tcell.EventMouse to let the action functions do the necessary checks (similarly to what MousePress is already doing). But then I realized it was a bad idea, since we still want to be able also to bind mouse events to regular key actions (such as PastePrimary) which don't care about mouse event info. --- internal/action/actions.go | 117 +++++++++++++++++++---------- internal/action/bindings.go | 13 +++- internal/action/bufpane.go | 86 +++++++++------------ internal/action/defaults_darwin.go | 22 +++--- internal/action/defaults_other.go | 22 +++--- internal/action/events.go | 23 +++++- runtime/help/keybindings.md | 28 ++++--- 7 files changed, 190 insertions(+), 121 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index dd92a364..bd37decd 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -51,53 +51,47 @@ func (h *BufPane) ScrollAdjust() { func (h *BufPane) MousePress(e *tcell.EventMouse) bool { b := h.Buf mx, my := e.Position() + // ignore click on the status line + if my >= h.BufView().Y+h.BufView().Height { + return false + } mouseLoc := h.LocFromVisual(buffer.Loc{mx, my}) h.Cursor.Loc = mouseLoc - if h.mouseReleased { - if b.NumCursors() > 1 { - b.ClearCursors() - h.Relocate() - h.Cursor = h.Buf.GetActiveCursor() - h.Cursor.Loc = mouseLoc - } - 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(clipboard.PrimaryReg) - } else { - // Double click - h.lastClickTime = time.Now() - - h.doubleClick = true - h.tripleClick = false - - h.Cursor.SelectWord() - h.Cursor.CopySelection(clipboard.PrimaryReg) - } - } else { - h.doubleClick = false - h.tripleClick = false + if b.NumCursors() > 1 { + b.ClearCursors() + h.Relocate() + h.Cursor = h.Buf.GetActiveCursor() + h.Cursor.Loc = mouseLoc + } + 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.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() + h.tripleClick = true + h.doubleClick = false + + h.Cursor.SelectLine() + h.Cursor.CopySelection(clipboard.PrimaryReg) } else { - h.Cursor.SetSelectionEnd(h.Cursor.Loc) + // Double click + h.lastClickTime = time.Now() + + h.doubleClick = true + h.tripleClick = false + + h.Cursor.SelectWord() + h.Cursor.CopySelection(clipboard.PrimaryReg) } + } 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.Cursor.StoreVisualX() @@ -106,6 +100,45 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool { return true } +func (h *BufPane) MouseDrag(e *tcell.EventMouse) bool { + mx, my := e.Position() + // ignore drag on the status line + if my >= h.BufView().Y+h.BufView().Height { + return false + } + h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my}) + + if h.tripleClick { + h.Cursor.AddLineToSelection() + } else if h.doubleClick { + h.Cursor.AddWordToSelection() + } else { + h.Cursor.SetSelectionEnd(h.Cursor.Loc) + } + + h.Cursor.StoreVisualX() + h.Relocate() + return true +} + +func (h *BufPane) MouseRelease(e *tcell.EventMouse) bool { + // We could finish the selection based on the release location as in the + // commented out code below, to allow text selections even in a terminal + // that doesn't support mouse motion events. But when the mouse click is + // within the scroll margin, that would cause a scroll and selection + // even for a simple mouse click, which is not good. + // if !h.doubleClick && !h.tripleClick { + // mx, my := e.Position() + // h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my}) + // h.Cursor.SetSelectionEnd(h.Cursor.Loc) + // } + + if h.Cursor.HasSelection() { + h.Cursor.CopySelection(clipboard.PrimaryReg) + } + return true +} + // ScrollUpAction scrolls the view up func (h *BufPane) ScrollUpAction() bool { h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"])) @@ -1794,6 +1827,10 @@ func (h *BufPane) SpawnMultiCursorSelect() bool { func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool { b := h.Buf mx, my := e.Position() + // ignore click on the status line + if my >= h.BufView().Y+h.BufView().Height { + return false + } mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my}) c := buffer.NewCursor(b, mouseLoc) b.AddCursor(c) diff --git a/internal/action/bindings.go b/internal/action/bindings.go index 846e3d11..e2bf10a0 100644 --- a/internal/action/bindings.go +++ b/internal/action/bindings.go @@ -201,11 +201,20 @@ modSearch: }, true } + var mstate MouseState = MousePress + if strings.HasSuffix(k, "Drag") { + k = k[:len(k)-4] + mstate = MouseDrag + } else if strings.HasSuffix(k, "Release") { + k = k[:len(k)-7] + mstate = MouseRelease + } // See if we can find the key in bindingMouse if code, ok := mouseEvents[k]; ok { return MouseEvent{ - btn: code, - mod: modifiers, + btn: code, + mod: modifiers, + state: mstate, }, true } diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index 847f5641..c6d527a1 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -8,7 +8,6 @@ import ( lua "github.com/yuin/gopher-lua" "github.com/zyedidia/micro/v2/internal/buffer" - "github.com/zyedidia/micro/v2/internal/clipboard" "github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/display" ulua "github.com/zyedidia/micro/v2/internal/lua" @@ -200,11 +199,15 @@ type BufPane struct { // Cursor is the currently active buffer cursor Cursor *buffer.Cursor - // Since tcell doesn't differentiate between a mouse release event - // and a mouse move event with no keys pressed, we need to keep - // track of whether or not the mouse was pressed (or not released) last event to determine - // mouse release events - mouseReleased bool + // Since tcell doesn't differentiate between a mouse press event + // and a mouse move event with button pressed (nor between a mouse + // release event and a mouse move event with no buttons pressed), + // we need to keep track of whether or not the mouse was previously + // pressed, to determine mouse release and mouse drag events. + // Moreover, since in case of a release event tcell doesn't tell us + // which button was released, we need to keep track of which + // (possibly multiple) buttons were pressed previously. + mousePressed map[MouseEvent]bool // We need to keep track of insert key press toggle isOverwriteMode bool @@ -250,7 +253,7 @@ func newBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane { h.tab = tab h.Cursor = h.Buf.GetActiveCursor() - h.mouseReleased = true + h.mousePressed = make(map[MouseEvent]bool) return h } @@ -332,7 +335,9 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) { h.initialRelocate() // Set mouseReleased to true because we assume the mouse is not being // pressed when the editor is opened - h.mouseReleased = true + for me := range h.mousePressed { + delete(h.mousePressed, me) + } // Set isOverwriteMode to false, because we assume we are in the default // mode when editor is opened h.isOverwriteMode = false @@ -432,50 +437,31 @@ func (h *BufPane) HandleEvent(event tcell.Event) { h.DoRuneInsert(e.Rune()) } case *tcell.EventMouse: - cancel := false - switch e.Buttons() { - case tcell.Button1: - _, my := e.Position() - if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 { - cancel = true - } - case tcell.ButtonNone: - // Mouse event with no click - if !h.mouseReleased { - // Mouse was just released - - // mx, my := e.Position() - // mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my}) - - // we could finish the selection based on the release location as described - // below but when the mouse click is within the scroll margin this will - // cause a scroll and selection even for a simple mouse click which is - // not good - // for terminals that don't support mouse motion events, selection via - // the mouse won't work but this is ok - - // 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.SetSelectionEnd(h.Cursor.Loc) - // } - if h.Cursor.HasSelection() { - h.Cursor.CopySelection(clipboard.PrimaryReg) - } - h.mouseReleased = true - } - } - - if !cancel { + if e.Buttons() != tcell.ButtonNone { me := MouseEvent{ - btn: e.Buttons(), - mod: metaToAlt(e.Modifiers()), + btn: e.Buttons(), + mod: metaToAlt(e.Modifiers()), + state: MousePress, } + if len(h.mousePressed) > 0 { + me.state = MouseDrag + } + + if e.Buttons() & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone { + h.mousePressed[me] = true + } + h.DoMouseEvent(me, e) + } else { + // Mouse event with no click - mouse was just released. + // If there were multiple mouse buttons pressed, we don't know which one + // was actually released, so we assume they all were released. + for me := range h.mousePressed { + delete(h.mousePressed, me) + + me.state = MouseRelease + h.DoMouseEvent(me, e) + } } } h.Buf.MergeCursors() @@ -788,6 +774,8 @@ var BufKeyActions = map[string]BufKeyAction{ // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute var BufMouseActions = map[string]BufMouseAction{ "MousePress": (*BufPane).MousePress, + "MouseDrag": (*BufPane).MouseDrag, + "MouseRelease": (*BufPane).MouseRelease, "MouseMultiCursor": (*BufPane).MouseMultiCursor, } diff --git a/internal/action/defaults_darwin.go b/internal/action/defaults_darwin.go index bae69910..558ca888 100644 --- a/internal/action/defaults_darwin.go +++ b/internal/action/defaults_darwin.go @@ -90,11 +90,13 @@ var bufdefaults = map[string]string{ "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch", // Mouse bindings - "MouseWheelUp": "ScrollUp", - "MouseWheelDown": "ScrollDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary", - "Ctrl-MouseLeft": "MouseMultiCursor", + "MouseWheelUp": "ScrollUp", + "MouseWheelDown": "ScrollDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary", + "Ctrl-MouseLeft": "MouseMultiCursor", "Alt-n": "SpawnMultiCursor", "AltShiftUp": "SpawnMultiCursorUp", @@ -173,8 +175,10 @@ var infodefaults = map[string]string{ "Esc": "AbortCommand", // Mouse bindings - "MouseWheelUp": "HistoryUp", - "MouseWheelDown": "HistoryDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary", + "MouseWheelUp": "HistoryUp", + "MouseWheelDown": "HistoryDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary", } diff --git a/internal/action/defaults_other.go b/internal/action/defaults_other.go index 39a9e0e3..d6ce27ea 100644 --- a/internal/action/defaults_other.go +++ b/internal/action/defaults_other.go @@ -92,11 +92,13 @@ var bufdefaults = map[string]string{ "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch", // Mouse bindings - "MouseWheelUp": "ScrollUp", - "MouseWheelDown": "ScrollDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary", - "Ctrl-MouseLeft": "MouseMultiCursor", + "MouseWheelUp": "ScrollUp", + "MouseWheelDown": "ScrollDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary", + "Ctrl-MouseLeft": "MouseMultiCursor", "Alt-n": "SpawnMultiCursor", "Alt-m": "SpawnMultiCursorSelect", @@ -175,8 +177,10 @@ var infodefaults = map[string]string{ "Esc": "AbortCommand", // Mouse bindings - "MouseWheelUp": "HistoryUp", - "MouseWheelDown": "HistoryDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary", + "MouseWheelUp": "HistoryUp", + "MouseWheelDown": "HistoryDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary", } diff --git a/internal/action/events.go b/internal/action/events.go index 3a4834f6..4addf1b5 100644 --- a/internal/action/events.go +++ b/internal/action/events.go @@ -100,11 +100,20 @@ func (k KeySequenceEvent) Name() string { return buf.String() } +type MouseState int + +const ( + MousePress = iota + MouseDrag + MouseRelease +) + // MouseEvent is a mouse event with a mouse button and // any possible key modifiers type MouseEvent struct { - btn tcell.ButtonMask - mod tcell.ModMask + btn tcell.ButtonMask + mod tcell.ModMask + state MouseState } func (m MouseEvent) Name() string { @@ -122,9 +131,17 @@ func (m MouseEvent) Name() string { mod = "Ctrl-" } + state := "" + switch m.state { + case MouseDrag: + state = "Drag" + case MouseRelease: + state = "Release" + } + for k, v := range mouseEvents { if v == m.btn { - return fmt.Sprintf("%s%s", mod, k) + return fmt.Sprintf("%s%s%s", mod, k, state) } } return "" diff --git a/runtime/help/keybindings.md b/runtime/help/keybindings.md index 8d14da7d..753e6fdf 100644 --- a/runtime/help/keybindings.md +++ b/runtime/help/keybindings.md @@ -407,8 +407,14 @@ mouse actions) ``` MouseLeft +MouseLeftDrag +MouseLeftRelease MouseMiddle +MouseMiddleDrag +MouseMiddleRelease MouseRight +MouseRightDrag +MouseRightRelease MouseWheelUp MouseWheelDown MouseWheelLeft @@ -520,11 +526,13 @@ conventions for text editing defaults. "Esc": "Escape", // Mouse bindings - "MouseWheelUp": "ScrollUp", - "MouseWheelDown": "ScrollDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary", - "Ctrl-MouseLeft": "MouseMultiCursor", + "MouseWheelUp": "ScrollUp", + "MouseWheelDown": "ScrollDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary", + "Ctrl-MouseLeft": "MouseMultiCursor", "Alt-n": "SpawnMultiCursor", "AltShiftUp": "SpawnMultiCursorUp", @@ -629,10 +637,12 @@ are given below: "Esc": "AbortCommand", // Mouse bindings - "MouseWheelUp": "HistoryUp", - "MouseWheelDown": "HistoryDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary" + "MouseWheelUp": "HistoryUp", + "MouseWheelDown": "HistoryDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary" } } ``` From 124fa9e2e7b9c7501a32cb528f28a52354de3840 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Thu, 3 Nov 2022 00:14:26 +0100 Subject: [PATCH 02/43] Fix up double release event after drag If we press mouse, drag and then release, the release event is generated twice, since both mouse press and mouse drag events have been saved in mousePressed map. To fix that, ensure that we only store mouse press events in it. --- internal/action/bufpane.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index c6d527a1..f40efa7e 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -443,14 +443,15 @@ func (h *BufPane) HandleEvent(event tcell.Event) { mod: metaToAlt(e.Modifiers()), state: MousePress, } - if len(h.mousePressed) > 0 { - me.state = MouseDrag - } + isDrag := len(h.mousePressed) > 0 if e.Buttons() & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone { h.mousePressed[me] = true } + if isDrag { + me.state = MouseDrag + } h.DoMouseEvent(me, e) } else { // Mouse event with no click - mouse was just released. From e5093892fd92c0721dad1e7f0c8d135f1f09e0b6 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Sat, 5 Nov 2022 11:41:04 +0100 Subject: [PATCH 03/43] Reset mouse release state after restarting the screen When we temporarily disable the screen (e.g. during TermMessage or RunInteractiveShell), if the mouse is pressed when the screen is still active and then released when the screen is already stopped, we aren't able to catch this mouse release event, so we erroneously think that the mouse is still pressed after the screen is restarted. This results in wrong behavior due to a mouse press event treated as a mouse move event, e.g. upon the left button click we see an unexpected text selection. So need to reset the mouse release state to "released" after restarting the screen, assuming it is always released when the screen is restarted. --- internal/action/bufpane.go | 10 +++++++--- internal/action/tab.go | 15 +++++++++++++++ internal/screen/screen.go | 8 ++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index f40efa7e..9cb3808b 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -325,6 +325,12 @@ func (h *BufPane) PluginCBRune(cb string, r rune) bool { return b } +func (h *BufPane) resetMouse() { + for me := range h.mousePressed { + delete(h.mousePressed, me) + } +} + // OpenBuffer opens the given buffer in this pane. func (h *BufPane) OpenBuffer(b *buffer.Buffer) { h.Buf.Close() @@ -335,9 +341,7 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) { h.initialRelocate() // Set mouseReleased to true because we assume the mouse is not being // pressed when the editor is opened - for me := range h.mousePressed { - delete(h.mousePressed, me) - } + h.resetMouse() // Set isOverwriteMode to false, because we assume we are in the default // mode when editor is opened h.isOverwriteMode = false diff --git a/internal/action/tab.go b/internal/action/tab.go index 07a866ce..0967df3a 100644 --- a/internal/action/tab.go +++ b/internal/action/tab.go @@ -161,6 +161,21 @@ func InitTabs(bufs []*buffer.Buffer) { } } } + + screen.RestartCallback = func() { + // The mouse could be released after the screen was stopped, so that + // we couldn't catch the mouse release event and would erroneously think + // that it is still pressed. So need to reset the mouse release state + // after the screen is restarted. + for _, t := range Tabs.List { + t.release = true + for _, p := range t.Panes { + if bp, ok := p.(*BufPane); ok { + bp.resetMouse() + } + } + } + } } func MainTab() *Tab { diff --git a/internal/screen/screen.go b/internal/screen/screen.go index 339e69aa..16c011e6 100644 --- a/internal/screen/screen.go +++ b/internal/screen/screen.go @@ -22,6 +22,10 @@ var Screen tcell.Screen // Events is the channel of tcell events var Events chan (tcell.Event) +// RestartCallback is called when the screen is restarted after it was +// temporarily shut down +var RestartCallback func() + // The lock is necessary since the screen is polled on a separate thread var lock sync.Mutex @@ -134,6 +138,10 @@ func TempStart(screenWasNil bool) { if !screenWasNil { Init() Unlock() + + if RestartCallback != nil { + RestartCallback() + } } } From 2d95064ff61e19941c516d3d819c09e57c5076f0 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Thu, 1 Dec 2022 22:53:38 +0100 Subject: [PATCH 04/43] Make a pane active whenever any mouse button is pressed on it Since now bufpane handles mouse move and release events generically and separately from mouse press events, that creates a mess when we dispatch a mouse press event to an inactive pane without making it active. For example: 1. Click the right button on an inactive pane. It remains inactive. 2. Then click the left button on it. It becomes active, and an unexpected text selection appears. The reason is that the release event for the first click was dispatched to a wrong pane - the (then) active one, so the (then) inactive pane didn't get the release event and treats the second click not as a mouse press but as a mouse move. The simplest way to fix it is to avoid this scenario entirely, i.e. always activate the pane when clicking any mouse button on it, not just the left button. For mouse wheel motion events we keep the existing behavior: the pane gets the event but doesn't become active. Mouse wheel motion events are not affected by the described issue, as they have no paired "release" events. --- internal/action/tab.go | 51 +++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/internal/action/tab.go b/internal/action/tab.go index 0967df3a..4227547f 100644 --- a/internal/action/tab.go +++ b/internal/action/tab.go @@ -226,34 +226,40 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab { // HandleEvent takes a tcell event and usually dispatches it to the current // active pane. However if the event is a resize or a mouse event where the user // is interacting with the UI (resizing splits) then the event is consumed here -// If the event is a mouse event in a pane, that pane will become active and get -// the event +// If the event is a mouse press event in a pane, that pane will become active +// and get the event func (t *Tab) HandleEvent(event tcell.Event) { switch e := event.(type) { case *tcell.EventMouse: mx, my := e.Position() - switch e.Buttons() { - case tcell.Button1: + btn := e.Buttons() + switch { + case btn & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone: + // button press or drag wasReleased := t.release t.release = false - if t.resizing != nil { - var size int - if t.resizing.Kind == views.STVert { - size = mx - t.resizing.X - } else { - size = my - t.resizing.Y + 1 + + if btn == tcell.Button1 { + if t.resizing != nil { + var size int + if t.resizing.Kind == views.STVert { + size = mx - t.resizing.X + } else { + size = my - t.resizing.Y + 1 + } + t.resizing.ResizeSplit(size) + t.Resize() + return + } + if wasReleased { + t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my}) + if t.resizing != nil { + return + } } - t.resizing.ResizeSplit(size) - t.Resize() - return } if wasReleased { - t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my}) - if t.resizing != nil { - return - } - for i, p := range t.Panes { v := p.GetView() inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height @@ -263,10 +269,15 @@ func (t *Tab) HandleEvent(event tcell.Event) { } } } - case tcell.ButtonNone: - t.resizing = nil + case btn == tcell.ButtonNone: + // button release t.release = true + if t.resizing != nil { + t.resizing = nil + return + } default: + // wheel move for _, p := range t.Panes { v := p.GetView() inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height From 2d82362a6695a7e898455ce016449167ac439ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Sun, 10 Dec 2023 22:52:22 +0100 Subject: [PATCH 05/43] actions: saveas: Fix crash at access without permission (#3082) --- internal/action/actions.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index 74e701f4..d45f5b2f 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -793,25 +793,26 @@ func (h *BufPane) SaveAsCB(action string, callback func()) bool { filename := strings.Join(args, " ") fileinfo, err := os.Stat(filename) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrPermission) { noPrompt := h.saveBufToFile(filename, action, callback) if noPrompt { h.completeAction(action) return } } - } - InfoBar.YNPrompt( - fmt.Sprintf("the file %s already exists in the directory, would you like to overwrite? Y/n", fileinfo.Name()), - func(yes, canceled bool) { - if yes && !canceled { - noPrompt := h.saveBufToFile(filename, action, callback) - if noPrompt { - h.completeAction(action) + } else { + InfoBar.YNPrompt( + fmt.Sprintf("The file %s already exists in the directory, would you like to overwrite? Y/n", fileinfo.Name()), + func(yes, canceled bool) { + if yes && !canceled { + noPrompt := h.saveBufToFile(filename, action, callback) + if noPrompt { + h.completeAction(action) + } } - } - }, - ) + }, + ) + } } }) return false From 4e383dd1100ee35e08cafcbebdcefdae330b3323 Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Wed, 17 Jan 2024 10:04:18 +0200 Subject: [PATCH 06/43] Do correct cursor right with storing visual X in `CursorRight` action (#3103) Signed-off-by: Yevhen Babiichuk (DustDFG) --- internal/action/actions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index d45f5b2f..be197d74 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -211,7 +211,7 @@ func (h *BufPane) CursorLeft() bool { func (h *BufPane) CursorRight() bool { if h.Cursor.HasSelection() { h.Cursor.Deselect(false) - h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf) + h.Cursor.Right() } else { tabstospaces := h.Buf.Settings["tabstospaces"].(bool) tabmovement := h.Buf.Settings["tabmovement"].(bool) From 422305af99d5729aa5cb555b1a1246e490f38e48 Mon Sep 17 00:00:00 2001 From: niten94 <127052329+niten94@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:06:14 +0800 Subject: [PATCH 07/43] Set bits in mode used when opening files (#3095) Set write permission bits of group and other users in mode used when opening files. --- internal/buffer/save.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/buffer/save.go b/internal/buffer/save.go index 6829d4a4..0a10a2dc 100644 --- a/internal/buffer/save.go +++ b/internal/buffer/save.go @@ -54,7 +54,7 @@ func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, screen.TempStart(screenb) return err } - } else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil { + } else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666); err != nil { return } From e5a9b906f35f43b4418fa05bc0fcc335bcf3e1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Wed, 17 Jan 2024 09:06:45 +0100 Subject: [PATCH 08/43] infocomplete: Complete filetypes (#3090) --- internal/action/infocomplete.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/action/infocomplete.go b/internal/action/infocomplete.go index 12f7844b..252ee2b3 100644 --- a/internal/action/infocomplete.go +++ b/internal/action/infocomplete.go @@ -77,6 +77,24 @@ func colorschemeComplete(input string) (string, []string) { return chosen, suggestions } +// filetypeComplete autocompletes filetype +func filetypeComplete(input string) (string, []string) { + var suggestions []string + + for _, f := range config.ListRuntimeFiles(config.RTSyntax) { + if strings.HasPrefix(f.Name(), input) { + suggestions = append(suggestions, f.Name()) + } + } + + var chosen string + if len(suggestions) == 1 { + chosen = suggestions[0] + } + + return chosen, suggestions +} + func contains(s []string, e string) bool { for _, a := range s { if a == e { @@ -172,6 +190,8 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) { switch inputOpt { case "colorscheme": _, suggestions = colorschemeComplete(input) + case "filetype": + _, suggestions = filetypeComplete(input) case "fileformat": if strings.HasPrefix("unix", input) { suggestions = append(suggestions, "unix") From fce8db80de33b313f04cd4b34f68732b3d276f8b Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Wed, 17 Jan 2024 10:07:51 +0200 Subject: [PATCH 09/43] Add go.mod syntax support (#3061) Signed-off-by: Yevhen Babiichuk (DustDFG) --- runtime/syntax/gomod.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 runtime/syntax/gomod.yaml diff --git a/runtime/syntax/gomod.yaml b/runtime/syntax/gomod.yaml new file mode 100644 index 00000000..5f2b7c68 --- /dev/null +++ b/runtime/syntax/gomod.yaml @@ -0,0 +1,31 @@ +filetype: gomod + +detect: + filename: "go.mod" + +rules: + # URL + - type: "(^|[ \\t])+\\b([a-zA-Z0-9-]+\\.?)+(/[a-zA-Z0-9-_\\.]+)*\\b" + + # Keywords + - special: "(^|[ \\t])+\\b(module|go)\\b" + - preproc: "(^|[ \\t])+\\b(toolchain|require|exclude|replace|retract)\\b" + - symbol.operator: "=>" + + # Brackets + - type: "(\\(|\\))" + + # Go version + - type: "(^|[ \\t])+([0-9]+\\.?)+" + + # Version + - constant.string: "(^|[ \\t])+v([0-9]+\\.?){3}.*" + - constant.number: "(^|[ \\t])+v([0-9]+\\.?){3}" + + - comment: + start: "//" + end: "$" + rules: + - todo: "(indirect):?" + +# (^|[ \\t])+ means after start of string or space or tab character From 59dda01cb7ad92a069068b3aa93abc7ff04cc935 Mon Sep 17 00:00:00 2001 From: Dmytro Maluka Date: Wed, 17 Jan 2024 09:09:33 +0100 Subject: [PATCH 10/43] Make plugins in ~/.config/micro/plug dir override built-in plugins (#3031) If ~/.config/micro/plug directory contains a plugin with the same name as a built-in plugin, the expected behavior is that the user-defined plugin in ~/.config/micro/plug is loaded instead of the built-in one. Whereas the existing behavior is that the built-in plugin is used instead of the user-defined one. Even worse, it is buggy: in this case the plugin is registered twice, so its callbacks are executed twice (e.g. with the autoclose plugin, a bracket is autoclosed with two closing brackets instead of one). Fix this by ensuring that if a plugin with the same name exists in the ~/.config/micro/plug directory, the built-in one is ignored. Fixes #3029 --- internal/config/rtfiles.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/config/rtfiles.go b/internal/config/rtfiles.go index fb4497e6..b1d6de22 100644 --- a/internal/config/rtfiles.go +++ b/internal/config/rtfiles.go @@ -218,7 +218,15 @@ func InitRuntimeFiles() { plugdir = filepath.Join("runtime", "plugins") if files, err := rt.AssetDir(plugdir); err == nil { + outer: for _, d := range files { + for _, p := range Plugins { + if p.Name == d { + log.Println(p.Name, "built-in plugin overridden by user-defined one") + continue outer + } + } + if srcs, err := rt.AssetDir(filepath.Join(plugdir, d)); err == nil { p := new(Plugin) p.Name = d From af2ec9d540e9e0ac736113435cd76a353d7e76aa Mon Sep 17 00:00:00 2001 From: Dmytro Maluka Date: Mon, 4 Mar 2024 22:20:02 +0100 Subject: [PATCH 11/43] Make default fileformat value suited to the OS (#3141) Set fileformat by default to `dos` on Windows. --- internal/config/settings.go | 10 +++++++++- runtime/help/options.md | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/config/settings.go b/internal/config/settings.go index 72e998f1..05a9f8a4 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "reflect" + "runtime" "strconv" "strings" @@ -284,7 +285,7 @@ var defaultCommonSettings = map[string]interface{}{ "encoding": "utf-8", "eofnewline": true, "fastdirty": false, - "fileformat": "unix", + "fileformat": defaultFileFormat(), "filetype": "unknown", "hlsearch": false, "incsearch": true, @@ -319,6 +320,13 @@ var defaultCommonSettings = map[string]interface{}{ "wordwrap": false, } +func defaultFileFormat() string { + if runtime.GOOS == "windows" { + return "dos" + } + return "unix" +} + func GetInfoBarOffset() int { offset := 0 if GetGlobalOption("infobar").(bool) { diff --git a/runtime/help/options.md b/runtime/help/options.md index 3170dc4c..75e877e8 100644 --- a/runtime/help/options.md +++ b/runtime/help/options.md @@ -157,7 +157,7 @@ Here are the available options: an effect if the file is empty/newly created, because otherwise the fileformat will be automatically detected from the existing line endings. - default value: `unix` + default value: `unix` on Unix systems, `dos` on Windows * `filetype`: sets the filetype for the current buffer. Set this option to `off` to completely disable filetype detection. From e5026ef3fae63d13d5e6612b244c030cf4f50a3a Mon Sep 17 00:00:00 2001 From: Dmytro Maluka Date: Mon, 4 Mar 2024 22:21:50 +0100 Subject: [PATCH 12/43] Make MouseMultiCursor toggle cursors (#3146) It is useful to be able to use mouse not only for adding new cursors but also for removing them. So let's modify MouseMultiCursor behavior: if a cursor already exists at the mouse click location, remove it. --- internal/action/actions.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index be197d74..bfbf2354 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -1828,11 +1828,23 @@ func (h *BufPane) SpawnMultiCursorSelect() bool { return true } -// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position +// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position, +// or removes a cursor if it is already there func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool { b := h.Buf mx, my := e.Position() mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my}) + + if h.Buf.NumCursors() > 1 { + cursors := h.Buf.GetCursors() + for _, c := range cursors { + if c.Loc == mouseLoc { + h.Buf.RemoveCursor(c.Num) + return true + } + } + } + c := buffer.NewCursor(b, mouseLoc) b.AddCursor(c) b.MergeCursors() From eedebd80d47ba5a023cceb6adb46105596314c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:22:47 +0100 Subject: [PATCH 13/43] util: Fix opening filenames including colons with `parsecursor` (#3119) The regex pattern shall search for the end of the filename first as it does while opening with +LINE:COL. --- internal/util/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/util/util.go b/internal/util/util.go index 81448f0d..fb21c487 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -315,7 +315,7 @@ func ReplaceHome(path string) (string, error) { // This is used for opening files like util.go:10:5 to specify a line and column // Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly. func GetPathAndCursorPosition(path string) (string, []string) { - re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`) + re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?$`) match := re.FindStringSubmatch(path) // no lines/columns were specified in the path, return just the path with no cursor location if len(match) == 0 { From 9fdea8254250b198e02cfdcddde5d4f059900e67 Mon Sep 17 00:00:00 2001 From: Dmytro Maluka Date: Mon, 4 Mar 2024 22:23:50 +0100 Subject: [PATCH 14/43] Fix various issues with `SpawnMultiCursor{Up,Down}` (#3145) * SpawnMultiCursorUp/Down: change order of adding cursors SpawnMultiCursor{Up,Down} currently works in a tricky way: instead of creating a new cursor above or below, it moves the current "primary" cursor above or below, and then creates a new cursor below or above the new position of the current cursor (i.e. at its previous position), creating an illusion for the user that the current (top-most or bottom-most) cursor is a newly spawned cursor. This trick causes at least the following issues: - When the line above or below, where we spawn a new cursor, is shorter than the current cursor position in the current line, the new cursor is placed at the end of this short line (which is expected), but also the current cursor unexpectedly changes its x position and moves below/above the new cursor. - When removing a cursor in RemoveMultiCursor (default Alt-p key), it non-intuitively removes the cursor which, from the user point of view, is not the last but the last-but-one cursor. Fix these issues by replacing the trick with a straightforward logic: just create the new cursor above or below the last one. Note that this fix has a user-visible side effect: the last cursor is no longer the "primary" one (since it is now the last in the list, not the first), so e.g. when the user clears multicursors via Esc key, the remaining cursor is the first one, not the last one. I assume it's ok. * SpawnMultiCursorUp/Down: move common code to a helper fn * SpawnMultiCursorUp/Down: honor visual width and LastVisualX Make spawning multicursors up/down behave more similarly to cursor movements up/down. This change fixes 2 issues at once: - SpawnMultiCursorUp/Down doesn't take into account the visual width of the text before the cursor, which may be different from its character width (e.g. if it contains tabs). So e.g. if the number of tabs before the cursor in the current line is not the same as in the new line, the new cursor is placed at an unexpected location. - SpawnMultiCursorUp/Down doesn't take into account the cursor's remembered x position (LastVisualX) when e.g. spawning a new cursor in the below line which is short than the current cursor position, and then spawning yet another cursor in the next below line which is longer than this short line. * SpawnMultiCursorUp/Down: honor softwrap When softwrap is enabled and the current line is wrapped, make SpawnMultiCursor{Up,Down} spawn cursor in the next visual line within this wrapped line, similarly to how we handle cursor movements up/down within wrapped lines. * SpawnMultiCursorUp/Down: deselect when spawning cursors To avoid weird user experience (spawned cursors messing with selections of existing cursors). --- internal/action/actions.go | 58 +++++++++++++++++++++++++------------- internal/buffer/buffer.go | 7 +++++ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index bfbf2354..ea0255f9 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -1761,15 +1761,39 @@ func (h *BufPane) SpawnMultiCursor() bool { return true } -// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less. -func (h *BufPane) SpawnMultiCursorUp() bool { - if h.Cursor.Y == 0 { - return false - } - h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1}) - h.Cursor.Relocate() +// SpawnMultiCursorUpN is not an action +func (h *BufPane) SpawnMultiCursorUpN(n int) bool { + lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1) + var c *buffer.Cursor + if !h.Buf.Settings["softwrap"].(bool) { + if n > 0 && lastC.Y == 0 { + return false + } + if n < 0 && lastC.Y+1 == h.Buf.LinesNum() { + return false + } + + h.Buf.DeselectCursors() + + c = buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n}) + c.LastVisualX = lastC.LastVisualX + c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX) + c.Relocate() + } else { + vloc := h.VLocFromLoc(lastC.Loc) + sloc := h.Scroll(vloc.SLoc, -n) + if sloc == vloc.SLoc { + return false + } + + h.Buf.DeselectCursors() + + vloc.SLoc = sloc + vloc.VisualX = lastC.LastVisualX + c = buffer.NewCursor(h.Buf, h.LocFromVLoc(vloc)) + c.LastVisualX = lastC.LastVisualX + } - c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1}) h.Buf.AddCursor(c) h.Buf.SetCurCursor(h.Buf.NumCursors() - 1) h.Buf.MergeCursors() @@ -1778,20 +1802,14 @@ func (h *BufPane) SpawnMultiCursorUp() bool { return true } +// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less. +func (h *BufPane) SpawnMultiCursorUp() bool { + return h.SpawnMultiCursorUpN(1) +} + // SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more. func (h *BufPane) SpawnMultiCursorDown() bool { - if h.Cursor.Y+1 == h.Buf.LinesNum() { - return false - } - h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1}) - h.Cursor.Relocate() - - c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1}) - h.Buf.AddCursor(c) - h.Buf.SetCurCursor(h.Buf.NumCursors() - 1) - h.Buf.MergeCursors() - h.Relocate() - return true + return h.SpawnMultiCursorUpN(-1) } // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index dc4d037f..7955e0bb 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -568,6 +568,13 @@ func (b *Buffer) RelocateCursors() { } } +// DeselectCursors removes selection from all cursors +func (b *Buffer) DeselectCursors() { + for _, c := range b.cursors { + c.Deselect(true) + } +} + // RuneAt returns the rune at a given location in the buffer func (b *Buffer) RuneAt(loc Loc) rune { line := b.LineBytes(loc.Y) From c15abea64c20066fc0b4c328dfabd3e6ba3253a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:24:40 +0100 Subject: [PATCH 15/43] rtfiles: Give user defined runtime files precedence over asset files (#3066) --- internal/config/rtfiles.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/config/rtfiles.go b/internal/config/rtfiles.go index b1d6de22..275831cf 100644 --- a/internal/config/rtfiles.go +++ b/internal/config/rtfiles.go @@ -129,9 +129,17 @@ func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) { if err != nil { return } + +assetLoop: for _, f := range files { if ok, _ := path.Match(pattern, f); ok { - AddRuntimeFile(fileType, assetFile(path.Join(directory, f))) + af := assetFile(path.Join(directory, f)) + for _, rf := range realFiles[fileType] { + if af.Name() == rf.Name() { + continue assetLoop + } + } + AddRuntimeFile(fileType, af) } } } From 0de16334d38411ca15d7e48b384750699f062bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:35:33 +0100 Subject: [PATCH 16/43] micro: Don't forward nil events into the sub event handler (#2992) --- cmd/micro/micro.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 3e6756bc..ac69c118 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -449,6 +449,10 @@ func DoEvent() { os.Exit(0) } + if event == nil { + return + } + if e, ok := event.(*tcell.EventError); ok { log.Println("tcell event error: ", e.Error()) @@ -469,12 +473,10 @@ func DoEvent() { } ulua.Lock.Lock() - // if event != nil { if action.InfoBar.HasPrompt { action.InfoBar.HandleEvent(event) } else { action.Tabs.HandleEvent(event) } - // } ulua.Lock.Unlock() } From 321322af317abde62bf46dfbbdb3be50aea11c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:49:24 +0100 Subject: [PATCH 17/43] micro: DoEvent: Don't forward the resize event into the InfoBar (#3035) --- cmd/micro/micro.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index ac69c118..e0ef0987 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -473,7 +473,8 @@ func DoEvent() { } ulua.Lock.Lock() - if action.InfoBar.HasPrompt { + _, resize := event.(*tcell.EventResize) + if action.InfoBar.HasPrompt && !resize { action.InfoBar.HandleEvent(event) } else { action.Tabs.HandleEvent(event) From 15b36ce0d6df8a1a0df4ea31e7e3868d3693b9cf Mon Sep 17 00:00:00 2001 From: blt-r <63462729+blt-r@users.noreply.github.com> Date: Tue, 12 Mar 2024 23:58:09 +0400 Subject: [PATCH 18/43] Remove the `NoDisplay=true` from desktop file (#3171) TUI apps usually have their desktop files visible in system menus. Also, flathub no longer allows apps with invisible desktop files. --- assets/packaging/micro.desktop | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/packaging/micro.desktop b/assets/packaging/micro.desktop index 35772518..3066f82e 100644 --- a/assets/packaging/micro.desktop +++ b/assets/packaging/micro.desktop @@ -12,5 +12,4 @@ Keywords=text;editor;syntax;terminal; Exec=micro %F StartupNotify=false Terminal=true -NoDisplay=true MimeType=text/plain;text/x-chdr;text/x-csrc;text/x-c++hdr;text/x-c++src;text/x-java;text/x-dsrc;text/x-pascal;text/x-perl;text/x-python;application/x-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/xml;text/html;text/css;text/x-sql;text/x-diff; From 3fce03dfd09c6bac77e74dd26b545c943e1c0e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:11:20 +0100 Subject: [PATCH 19/43] syntax: sh: Fix command parameter highlighting (#3128) --- runtime/syntax/sh.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/syntax/sh.yaml b/runtime/syntax/sh.yaml index ab47bf1d..b9de0552 100644 --- a/runtime/syntax/sh.yaml +++ b/runtime/syntax/sh.yaml @@ -39,7 +39,7 @@ rules: # Coreutils commands - type: "\\b(base64|basename|cat|chcon|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|dd|df|dir|dircolors|dirname|du|env|expand|expr|factor|false|fmt|fold|head|hostid|id|install|join|link|ln|logname|ls|md5sum|mkdir|mkfifo|mknod|mktemp|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|pr|printenv|printf|ptx|pwd|readlink|realpath|rm|rmdir|runcon|seq|(sha1|sha224|sha256|sha384|sha512)sum|shred|shuf|sleep|sort|split|stat|stdbuf|stty|sum|sync|tac|tail|tee|test|time|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|users|vdir|wc|who|whoami|yes)\\b" # Conditional flags - - statement: " (-[A-Za-z]+|--[a-z]+)" + - statement: "\\s+(-[A-Za-z]+|--[a-z]+)" - identifier: "\\$\\{[0-9A-Za-z_:!%&=+#~@*^$?, .\\-\\/\\[\\]]+\\}" - identifier: "\\$[0-9A-Za-z_:!%&=+#~@*^$?,\\-\\[\\]]+" From 88b4498ce0294beb286953742719524d442da301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 12 Mar 2024 21:20:03 +0100 Subject: [PATCH 20/43] Some syntax highlighting updates for C and C#. (#3125) * Update C syntax with keywords up to C23. * Update C syntax with some GCC extensions. * Update C# syntax with new keywords up to C# 12. * Update C# syntax with preprocessor directives. * Add Cake build script (C#) syntax. * Add MSBuild (XML) syntax. --- runtime/syntax/c.yaml | 18 +++++++++++------- runtime/syntax/cake.yaml | 7 +++++++ runtime/syntax/csharp.yaml | 7 ++++--- runtime/syntax/msbuild.yaml | 6 ++++++ 4 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 runtime/syntax/cake.yaml create mode 100644 runtime/syntax/msbuild.yaml diff --git a/runtime/syntax/c.yaml b/runtime/syntax/c.yaml index cf19b25a..4d603234 100644 --- a/runtime/syntax/c.yaml +++ b/runtime/syntax/c.yaml @@ -5,18 +5,22 @@ detect: rules: - identifier: "\\b[A-Z_][0-9A-Z_]+\\b" - - type: "\\b(float|double|bool|char|int|short|long|enum|void|struct|union|typedef|(un)?signed|inline)\\b" - - type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\\b" + - type: "\\b(_Atomic|_BitInt|float|double|_Decimal32|_Decimal64|_Decimal128|_Complex|complex|_Imaginary|imaginary|_Bool|bool|char|int|short|long|enum|void|struct|union|typedef|typeof|typeof_unqual|(un)?signed|inline|_Noreturn)\\b" + - type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr))|char(8|16|32)|wchar)_t\\b" + # GCC float/decimal/fixed types + - type: "\\b(_Float16|__fp16|_Float32|_Float32x|_Float64|_Float64x|__float80|_Float128|_Float128x|__float128|__ibm128|__int128|_Fract|_Sat|_Accum)\\b" - type: "\\b[a-z_][0-9a-z_]+(_t|_T)\\b" - - statement: "\\b(auto|volatile|register|restrict|static|const|extern)\\b" - - statement: "\\b(for|if|while|do|else|case|default|switch)\\b" + - statement: "\\b(auto|volatile|register|restrict|_Alignas|alignas|_Alignof|alignof|static|const|constexpr|extern|_Thread_local|thread_local)\\b" + - statement: "\\b(for|if|while|do|else|case|default|switch|_Generic|_Static_assert|static_assert)\\b" - statement: "\\b(goto|continue|break|return)\\b" - - preproc: "^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error)" + - statement: "\\b(asm|fortran)\\b" + - preproc: "^[[:space:]]*#[[:space:]]*(define|embed|pragma|include|(un|ifn?)def|endif|el(if|ifdef|ifndef|se)|if|line|warning|error|__has_include|__has_embed|__has_c_attribute)" + - preproc: "^[[:space:]]*_Pragma\\b" # GCC builtins - statement: "__attribute__[[:space:]]*\\(\\([^)]*\\)\\)" - - statement: "__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__" + - statement: "__(aligned|asm|builtin|extension|hidden|inline|packed|restrict|section|typeof|weak)__" # Operator Color - - symbol.operator: "[-+*/%=<>.:;,~&|^!?]|\\b(sizeof)\\b" + - symbol.operator: "[-+*/%=<>.:;,~&|^!?]|\\b(offsetof|sizeof)\\b" - symbol.brackets: "[(){}]|\\[|\\]" # Integer Constants - constant.number: "(\\b([1-9][0-9]*|0[0-7]*|0[Xx][0-9A-Fa-f]+|0[Bb][01]+)([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\b)" diff --git a/runtime/syntax/cake.yaml b/runtime/syntax/cake.yaml new file mode 100644 index 00000000..555b4812 --- /dev/null +++ b/runtime/syntax/cake.yaml @@ -0,0 +1,7 @@ +filetype: cake +detect: + filename: "\\.cake$" + +rules: + - include: "csharp" + - preproc: "^[[:space:]]*#(addin|break|l|load|module|r|reference|tool)" diff --git a/runtime/syntax/csharp.yaml b/runtime/syntax/csharp.yaml index 620aada0..e78a19cf 100644 --- a/runtime/syntax/csharp.yaml +++ b/runtime/syntax/csharp.yaml @@ -10,10 +10,11 @@ rules: # Annotation - identifier.var: "@[A-Za-z]+" + - preproc: "^[[:space:]]*#[[:space:]]*(define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)" - identifier: "([A-Za-z0-9_]*[[:space:]]*[()])" - - type: "\\b(bool|byte|sbyte|char|decimal|double|float|IntPtr|int|uint|long|ulong|object|short|ushort|string|base|this|var|void)\\b" - - statement: "\\b(alias|as|case|catch|checked|default|do|dynamic|else|finally|fixed|for|foreach|goto|if|is|lock|new|null|return|switch|throw|try|unchecked|while)\\b" - - statement: "\\b(abstract|async|class|const|delegate|enum|event|explicit|extern|get|implicit|in|internal|interface|namespace|operator|out|override|params|partial|private|protected|public|readonly|ref|sealed|set|sizeof|stackalloc|static|struct|typeof|unsafe|using|value|virtual|volatile|yield)\\b" + - type: "\\b(bool|byte|sbyte|char|decimal|double|float|IntPtr|int|uint|long|ulong|managed|unmanaged|nint|nuint|object|short|ushort|string|base|this|var|void)\\b" + - statement: "\\b(alias|as|case|catch|checked|default|do|dynamic|else|finally|fixed|for|foreach|goto|if|is|lock|new|null|return|switch|throw|try|unchecked|when|while|with)\\b" + - statement: "\\b(abstract|add|and|args|async|await|class|const|delegate|enum|event|explicit|extern|file|get|global|implicit|in|init|internal|interface|nameof|namespace|not|notnull|operator|or|out|override|params|partial|private|protected|public|readonly|record|ref|remove|required|scoped|sealed|set|sizeof|stackalloc|static|struct|typeof|unsafe|using|value|virtual|volatile|yield)\\b" # LINQ-only keywords (ones that cannot be used outside of a LINQ query - lots others can) - statement: "\\b(from|where|select|group|info|orderby|join|let|in|on|equals|by|ascending|descending)\\b" - special: "\\b(break|continue)\\b" diff --git a/runtime/syntax/msbuild.yaml b/runtime/syntax/msbuild.yaml new file mode 100644 index 00000000..2c792137 --- /dev/null +++ b/runtime/syntax/msbuild.yaml @@ -0,0 +1,6 @@ +filetype: msbuild +detect: + filename: "\\.(.*proj|props|targets|tasks)$" + +rules: + - include: "xml" From fe4ade78dfbe8b25a4bb290c06de1344c86b0690 Mon Sep 17 00:00:00 2001 From: taconi <44283700+taconi@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:23:08 -0300 Subject: [PATCH 21/43] feat: adds GetArg and GetWord methods to Buffer (#3112) --- internal/action/infocomplete.go | 12 ++++++------ internal/buffer/autocomplete.go | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/action/infocomplete.go b/internal/action/infocomplete.go index 252ee2b3..568b6691 100644 --- a/internal/action/infocomplete.go +++ b/internal/action/infocomplete.go @@ -17,7 +17,7 @@ import ( // CommandComplete autocompletes commands func CommandComplete(b *buffer.Buffer) ([]string, []string) { c := b.GetActiveCursor() - input, argstart := buffer.GetArg(b) + input, argstart := b.GetArg() var suggestions []string for cmd := range commands { @@ -38,7 +38,7 @@ func CommandComplete(b *buffer.Buffer) ([]string, []string) { // HelpComplete autocompletes help topics func HelpComplete(b *buffer.Buffer) ([]string, []string) { c := b.GetActiveCursor() - input, argstart := buffer.GetArg(b) + input, argstart := b.GetArg() var suggestions []string @@ -107,7 +107,7 @@ func contains(s []string, e string) bool { // OptionComplete autocompletes options func OptionComplete(b *buffer.Buffer) ([]string, []string) { c := b.GetActiveCursor() - input, argstart := buffer.GetArg(b) + input, argstart := b.GetArg() var suggestions []string for option := range config.GlobalSettings { @@ -134,7 +134,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) { c := b.GetActiveCursor() l := b.LineBytes(c.Y) l = util.SliceStart(l, c.X) - input, argstart := buffer.GetArg(b) + input, argstart := b.GetArg() completeValue := false args := bytes.Split(l, []byte{' '}) @@ -230,7 +230,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) { // PluginCmdComplete autocompletes the plugin command func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) { c := b.GetActiveCursor() - input, argstart := buffer.GetArg(b) + input, argstart := b.GetArg() var suggestions []string for _, cmd := range PluginCmds { @@ -252,7 +252,7 @@ func PluginComplete(b *buffer.Buffer) ([]string, []string) { c := b.GetActiveCursor() l := b.LineBytes(c.Y) l = util.SliceStart(l, c.X) - input, argstart := buffer.GetArg(b) + input, argstart := b.GetArg() completeValue := false args := bytes.Split(l, []byte{' '}) diff --git a/internal/buffer/autocomplete.go b/internal/buffer/autocomplete.go index 414e3060..7a8c5bee 100644 --- a/internal/buffer/autocomplete.go +++ b/internal/buffer/autocomplete.go @@ -64,7 +64,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) { // GetWord gets the most recent word separated by any separator // (whitespace, punctuation, any non alphanumeric character) -func GetWord(b *Buffer) ([]byte, int) { +func (b *Buffer) GetWord() ([]byte, int) { c := b.GetActiveCursor() l := b.LineBytes(c.Y) l = util.SliceStart(l, c.X) @@ -83,7 +83,7 @@ func GetWord(b *Buffer) ([]byte, int) { } // GetArg gets the most recent word (separated by ' ' only) -func GetArg(b *Buffer) (string, int) { +func (b *Buffer) GetArg() (string, int) { c := b.GetActiveCursor() l := b.LineBytes(c.Y) l = util.SliceStart(l, c.X) @@ -104,7 +104,7 @@ func GetArg(b *Buffer) (string, int) { // FileComplete autocompletes filenames func FileComplete(b *Buffer) ([]string, []string) { c := b.GetActiveCursor() - input, argstart := GetArg(b) + input, argstart := b.GetArg() sep := string(os.PathSeparator) dirs := strings.Split(input, sep) @@ -153,7 +153,7 @@ func FileComplete(b *Buffer) ([]string, []string) { // BufferComplete autocompletes based on previous words in the buffer func BufferComplete(b *Buffer) ([]string, []string) { c := b.GetActiveCursor() - input, argstart := GetWord(b) + input, argstart := b.GetWord() if argstart == -1 { return []string{}, []string{} From f0bc6281d4c1b2fc18d5184c9954c7db6597d8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AB=E3=81=A6=E3=82=93?= <127052329+niten94@users.noreply.github.com> Date: Wed, 13 Mar 2024 04:40:13 +0800 Subject: [PATCH 22/43] Add onRune parameter, utf8 package in plugins.md (#3100) Add bufpane in parameters of onRune, preRune, and unicode/utf8 Go package in plugins.md. --- runtime/help/plugins.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/help/plugins.md b/runtime/help/plugins.md index dc87476f..19dd0c3e 100644 --- a/runtime/help/plugins.md +++ b/runtime/help/plugins.md @@ -67,9 +67,9 @@ which micro defines: by the user. Returns a boolean which defines whether the action should be canceled. -* `onRune(rune)`: runs when the composed rune has been inserted +* `onRune(bufpane, rune)`: runs when the composed rune has been inserted -* `preRune(rune)`: runs before the composed rune will be inserted +* `preRune(bufpane, rune)`: runs before the composed rune will be inserted For example a function which is run every time the user saves the buffer would be: @@ -367,6 +367,7 @@ strings regexp errors time +unicode/utf8 archive/zip net/http ``` From fad4e449fb51585d2cb101639fa5447e98badc19 Mon Sep 17 00:00:00 2001 From: taconi <44283700+taconi@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:00:29 -0300 Subject: [PATCH 23/43] syntax: kvlang: add syntax highlight code for .kv files (#3106) --- runtime/syntax/kvlang.yaml | 67 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 runtime/syntax/kvlang.yaml diff --git a/runtime/syntax/kvlang.yaml b/runtime/syntax/kvlang.yaml new file mode 100644 index 00000000..e40d348f --- /dev/null +++ b/runtime/syntax/kvlang.yaml @@ -0,0 +1,67 @@ +filetype: "kvlang" + +detect: + filename: "\\.kv$" + +rules: +# layouts +- special: "\\b[a-z].+" +- identifier: "\\b(self|app|root)\\b" + +- type: "\\b[A-Z].+" +- type: "\\b(AnchorLayout|BoxLayout|FloatLayout|RelativeLayout|GridLayout|PageLayout|StackLayout)\\b" + +- type: "\\b(canvas)\\b" + +# functions +- identifier.function: "[a-zA-Z_0-9]+\\(" + +# built-in functions +- type: "\\b(abs|all|any|ascii|bin|bool|breakpoint|bytearray|bytes)\\b" +- type: "\\b(callable|chr|classmethod|compile|copyright|credits|oct)\\b" +- type: "\\b(delattr|dict|dir|display|divmod|enumerate|eval|filter)\\b" +- type: "\\b(float|format|frozenset|get_ipython|getattr|globals|type)\\b" +- type: "\\b(hash|help|hex|id|input|int|isinstance|issubclass|iter|len)\\b" +- type: "\\b(license|list|locals|map|max|memoryview|min|next|object)\\b" +- type: "\\b(open|ord|pow|print|property|range|repr|reversed|round|set)\\b" +- type: "\\b(setattr|slice|sorted|staticmethod|hasattr|super|tuple|str)\\b" +- type: "\\b(vars|zip|exec|sum|complex)\\b" + +# keywords +- statement.built_in: "\\b(and|as|assert|async|await|break|class|continue|def)\\b" +- statement.built_in: "\\b(del|elif|else|except|finally|for|from|global|if)\\b" +- statement.built_in: "\\b(import|in|is|lambda|nonlocal|not|or|pass|raise)\\b" +- statement.built_in: "\\b(return|try|while|with|yield|match|case)\\b" + +# operators +- symbol.operator: "([~^.:;,+*|=!\\%]|<|>|/|-|&)" + +# parentheses +- symbol.brackets: "([(){}]|\\[|\\])" + +# numbers +- constant.number: "\\b[0-9](_?[0-9])*(\\.([0-9](_?[0-9])*)?)?(e[0-9](_?[0-9])*)?\\b" # decimal +- constant.number: "\\b0b(_?[01])+\\b" # bin +- constant.number: "\\b0o(_?[0-7])+\\b" # oct +- constant.number: "\\b0x(_?[0-9a-f])+\\b" # hex + +- constant.bool.none: "\\b(None)\\b" +- constant.bool.true: "\\b(True)\\b" +- constant.bool.false: "\\b(False)\\b" + +# strings +- constant.string: + start: "\"" + end: "(\"|$)" + skip: "\\\\." + rules: [] +- constant.string: + start: "'" + end: "('|$)" + skip: "\\\\." + rules: [] + +- comment: + start: "#" + end: "$" + rules: [] From 14dca7d3494a225497c325cb571ce480d68d33b1 Mon Sep 17 00:00:00 2001 From: taconi <44283700+taconi@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:01:16 -0300 Subject: [PATCH 24/43] syntax: log: add syntax highlight code for log files (#3105) --- runtime/syntax/log.yaml | 94 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 runtime/syntax/log.yaml diff --git a/runtime/syntax/log.yaml b/runtime/syntax/log.yaml new file mode 100644 index 00000000..fa01238f --- /dev/null +++ b/runtime/syntax/log.yaml @@ -0,0 +1,94 @@ +filetype: log + +detect: + filename: "(\\.log|log\\.txt)$" + +rules: +- diff-modified: "\\b(WARN(ING)?|[Ww]arn(ing)?|w(r)?n|w|W/)\\b" +- diff-modified: "\\b(CRITICAL|[Cc]ritical)\\b" + +- constant: "\\b(INFO(RMATION)?|[Ii]nfo(rmation)?|[Ii]n(f)?|i|I/)\\b" +- constant: "\\b(DEBUG|[Dd]ebug|dbug|dbg|de|d|D/)\\b" +- constant: "\\b(VERBOSE|[Vv]erbose|V/)\\b" +- constant: "\\b(ALERT|[Aa]lert)\\b" + +- preproc: "\\b(TRACE|Trace|NOTICE|VERBOSE|verb|vrb|vb|v)\\b" + +- gutter-error: "\\b(ERROR|[Ee]rr(or)?|[Ee]r(or)?|e|E\\x2F)\\b" +- gutter-error: "\\b(FATAL|[Ff]atal)\\b" +- gutter-error: "\\b(EMERGENCY|[Ee]mergency)\\b" +- gutter-error: "\\b(FAIL(URE)?|[Ff]ail(ure)?)\\b" + +# constants +- constant.bool.true: "\\b(YES|yes|Y|y|ON|on|TRUE|True|true)\\b" +- constant.bool.false: "\\b(NO|no|N|n|OFF|off|FALSE|False|false)\\b" +- constant.bool.false: "\\b(None|null|nil)\\b" + +# numbers +- constant.number: "\\b[0-9](_?[0-9])*(\\.([0-9](_?[0-9])*)?)?(e[0-9](_?[0-9])*)?\\b" # decimal +- constant.number: "\\b0b(_?[01])+\\b" # bin +- constant.number: "\\b0o(_?[0-7])+\\b" # oct +- constant.number: "\\b0x(_?[0-9a-f])+\\b" # hex + +# operators +- symbol.operator: "([~^.:;,+*|=!\\%]|<|>|/|-|&)" + +# parentheses +- symbol.brackets: "([(){}]|\\[|\\])" + +# string +- constant.string: + start: "\"" + end: "(\"|$)" + skip: "\\\\." + rules: + - constant.specialChar: "\\\\." + +- constant.string: + start: "'" + end: "('|$)" + skip: "\\\\." + rules: + - constant.specialChar: "\\\\." + +# file +- preproc: "\\b(FILE|File|file)\\b" + +# time +- identifier: "\\b((([Mm]on|[Tt]ues|[Ww]ed(nes)?|[Tt]hur(s)?|[Ff]ri|[Ss]at(ur)?|[Ss]un)(day)?\\s)?([Jj]an(uary)?|[Ff]eb(ruary)?|[Mm]ar(ch)?|[Aa]pr(il)?|[Mm]ay|[Jj]un(e)?|[Jj]ul(y)?|[Aa]ug(ust)?|[Aa]go|[Ss]ep(tember)?|[Oo]ct(ober)?|[Nn]ov(ember)?|[Dd]ec(ember)?)\\s\\d{1,2},?(\\s\\d{4})?)\\b" # date +- identifier: "\\b(\\d{2,4}[-/\\.]?\\d{2,3}[-/\\.]?\\d{2,4})\\b" # date +- identifier: "\\b(\\d{2}:\\d{2}(:\\d{2})?([\\.,]?\\d{1,8}[\\.\\+,]?\\d{1,8}?)?([\\.\\+,]?\\d{1,8}[\\.\\+,]?\\d{1,8}?)?([\\.\\+,]?\\d{1,8}?)?(\\s-\\d{0,4})?)\\b" # time +- identifier: "^([0-2][0-9][0-2][0-9][-/]?[0-9][0-9][-/]?[0-9][0-9])" +# - identifier: "^([0-2][0-9][0-2][0-9][-/]?[0-9][0-9][-/]?[0-9][0-9]\\s[0-9][0-9]:[0-9][0-9](:[0-9][0-9])?(\\.?[0-9][0-9][0-9])?)" +- identifier: "^(\\d{4}[-/]?\\d{2}[-/]?\\d{2}\\s\\d{2}:\\d{2}(:\\d{2})?(\\.?\\d{2,8})?)" +- identifier: "^([0-2][0-9]|[0-2]-?[0-9][0-9]-?[0-9][0-9])\\-([0-1][0-9])\\-([0-3][0-9]) ([0-2][0-9])\\:([0-5][0-9])\\:([0-5][0-9]),([0-9][0-9][0-9])" +# ISO 8601:2004(E) +- identifier: "" +# Complete precision: +- identifier: "^(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))" +# No milliseconds: +- identifier: "^(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))" +# No Seconds: +- identifier: "^(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))" +# Putting it all together: +- identifier: "^(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))" +# Complete precision: +- identifier: "^(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+)" +# No milliseconds +- identifier: "^(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d)" +# No Seconds +- identifier: "^(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d)" +# Putting it all together +- identifier: "^(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+)|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d)|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d)" + +# link +- constant.string.url: + start: "https?://" + end: "\\s" + rules: [] + +# path +# - constant.string.url: "\\b(.+)/([^/]+)\\b" # linux +# - constant.string.url: "\\b(^[a-zA-Z]:)\\b" # windowns + +- diff-modified: "([Cc]ommit:)\\s\\w+\\[\\w+]" From 69eaa9191a551ce2112983f998599285d5c31dc4 Mon Sep 17 00:00:00 2001 From: toiletbril <36993107+toiletbril@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:21:27 +0300 Subject: [PATCH 25/43] options: add `matchbracestyle` (#2876) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update docs to include `matchbracestyle` * Add `matchbracestyle` to infocomplete.go * Add validator and default settings for `matchbracestyle` * Highlight or underline braces based on `matchbracestyle` * Add `match-brace` to default colorschemes * Correct `FindMatchingBrace()` counting Make brace under the cursor have priority over brace to the left in ambiguous cases when matching braces Co-authored-by: Dmitry Maluka * Fix conflicts --------- Co-authored-by: Jöran Karl <3951388+JoeKar@users.noreply.github.com> Co-authored-by: Dmitry Maluka --- data/micro.json | 11 +- internal/action/infocomplete.go | 7 ++ internal/buffer/buffer.go | 20 ++-- internal/config/settings.go | 128 +++++++++++++---------- internal/display/bufwindow.go | 14 ++- runtime/colorschemes/atom-dark.micro | 1 + runtime/colorschemes/bubblegum.micro | 1 + runtime/colorschemes/cmc-16.micro | 1 + runtime/colorschemes/cmc-tc.micro | 1 + runtime/colorschemes/darcula.micro | 1 + runtime/colorschemes/default.micro | 1 + runtime/colorschemes/dracula-tc.micro | 1 + runtime/colorschemes/dukedark-tc.micro | 1 + runtime/colorschemes/dukelight-tc.micro | 1 + runtime/colorschemes/dukeubuntu-tc.micro | 1 + runtime/colorschemes/geany.micro | 1 + runtime/colorschemes/gotham.micro | 1 + runtime/colorschemes/gruvbox-tc.micro | 1 + runtime/colorschemes/gruvbox.micro | 1 + runtime/colorschemes/material-tc.micro | 1 + runtime/colorschemes/monokai-dark.micro | 1 + runtime/colorschemes/monokai.micro | 1 + runtime/colorschemes/one-dark.micro | 1 + runtime/colorschemes/railscast.micro | 2 + runtime/colorschemes/simple.micro | 1 + runtime/colorschemes/solarized-tc.micro | 1 + runtime/colorschemes/solarized.micro | 1 + runtime/colorschemes/sunny-day.micro | 1 + runtime/colorschemes/twilight.micro | 1 + runtime/colorschemes/zenburn.micro | 1 + runtime/help/colors.md | 1 + runtime/help/options.md | 13 ++- 32 files changed, 150 insertions(+), 70 deletions(-) diff --git a/data/micro.json b/data/micro.json index 63ba098d..49fdc662 100644 --- a/data/micro.json +++ b/data/micro.json @@ -170,10 +170,19 @@ "default": false }, "matchbrace": { - "description": "Whether to underline matching braces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options", + "description": "Whether to show matching braces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options", "type": "boolean", "default": true }, + "matchbracestyle": { + "description": "Whether to underline or highlight matching braces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options", + "type": "string", + "enum": [ + "underline", + "highlight" + ], + "default": "underline" + }, "mkparents": { "description": "Whether to create missing directories\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options", "type": "boolean", diff --git a/internal/action/infocomplete.go b/internal/action/infocomplete.go index 568b6691..e093123d 100644 --- a/internal/action/infocomplete.go +++ b/internal/action/infocomplete.go @@ -216,6 +216,13 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) { if strings.HasPrefix("terminal", input) { suggestions = append(suggestions, "terminal") } + case "matchbracestyle": + if strings.HasPrefix("underline", input) { + suggestions = append(suggestions, "underline") + } + if strings.HasPrefix("highlight", input) { + suggestions = append(suggestions, "highlight") + } } } sort.Strings(suggestions) diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index 7955e0bb..7d7588c1 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -1015,7 +1015,7 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, boo leftChar = curLine[start.X-1] } var i int - if startChar == braceType[0] || leftChar == braceType[0] { + if startChar == braceType[0] || (leftChar == braceType[0] && startChar != braceType[1]) { for y := start.Y; y < b.LinesNum(); y++ { l := []rune(string(b.LineBytes(y))) xInit := 0 @@ -1046,24 +1046,24 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, boo l := []rune(string(b.lines[y].data)) xInit := len(l) - 1 if y == start.Y { - if leftChar == braceType[1] { - xInit = start.X - 1 - } else { + if startChar == braceType[1] { xInit = start.X + } else { + xInit = start.X - 1 } } for x := xInit; x >= 0; x-- { r := l[x] - if r == braceType[0] { + if r == braceType[1] { + i++ + } else if r == braceType[0] { i-- if i == 0 { - if leftChar == braceType[1] { - return Loc{x, y}, true, true + if startChar == braceType[1] { + return Loc{x, y}, false, true } - return Loc{x, y}, false, true + return Loc{x, y}, true, true } - } else if r == braceType[1] { - i++ } } } diff --git a/internal/config/settings.go b/internal/config/settings.go index 05a9f8a4..3b9bfaef 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -43,17 +43,18 @@ func init() { // Options with validators var optionValidators = map[string]optionValidator{ - "autosave": validateNonNegativeValue, - "clipboard": validateClipboard, - "tabsize": validatePositiveValue, - "scrollmargin": validateNonNegativeValue, - "scrollspeed": validateNonNegativeValue, - "colorscheme": validateColorscheme, - "colorcolumn": validateNonNegativeValue, - "fileformat": validateLineEnding, - "encoding": validateEncoding, - "multiopen": validateMultiOpen, - "reload": validateReload, + "autosave": validateNonNegativeValue, + "clipboard": validateClipboard, + "tabsize": validatePositiveValue, + "scrollmargin": validateNonNegativeValue, + "scrollspeed": validateNonNegativeValue, + "colorscheme": validateColorscheme, + "colorcolumn": validateNonNegativeValue, + "fileformat": validateLineEnding, + "encoding": validateEncoding, + "multiopen": validateMultiOpen, + "reload": validateReload, + "matchbracestyle": validateMatchBraceStyle, } func ReadSettings() error { @@ -274,50 +275,51 @@ func GetGlobalOption(name string) interface{} { } var defaultCommonSettings = map[string]interface{}{ - "autoindent": true, - "autosu": false, - "backup": true, - "backupdir": "", - "basename": false, - "colorcolumn": float64(0), - "cursorline": true, - "diffgutter": false, - "encoding": "utf-8", - "eofnewline": true, - "fastdirty": false, - "fileformat": defaultFileFormat(), - "filetype": "unknown", - "hlsearch": false, - "incsearch": true, - "ignorecase": true, - "indentchar": " ", - "keepautoindent": false, - "matchbrace": true, - "mkparents": false, - "permbackup": false, - "readonly": false, - "reload": "prompt", - "rmtrailingws": false, - "ruler": true, - "relativeruler": false, - "savecursor": false, - "saveundo": false, - "scrollbar": false, - "scrollmargin": float64(3), - "scrollspeed": float64(2), - "smartpaste": true, - "softwrap": false, - "splitbottom": true, - "splitright": true, - "statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)", - "statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help", - "statusline": true, - "syntax": true, - "tabmovement": false, - "tabsize": float64(4), - "tabstospaces": false, - "useprimary": true, - "wordwrap": false, + "autoindent": true, + "autosu": false, + "backup": true, + "backupdir": "", + "basename": false, + "colorcolumn": float64(0), + "cursorline": true, + "diffgutter": false, + "encoding": "utf-8", + "eofnewline": true, + "fastdirty": false, + "fileformat": defaultFileFormat(), + "filetype": "unknown", + "hlsearch": false, + "incsearch": true, + "ignorecase": true, + "indentchar": " ", + "keepautoindent": false, + "matchbrace": true, + "matchbracestyle": "underline", + "mkparents": false, + "permbackup": false, + "readonly": false, + "reload": "prompt", + "rmtrailingws": false, + "ruler": true, + "relativeruler": false, + "savecursor": false, + "saveundo": false, + "scrollbar": false, + "scrollmargin": float64(3), + "scrollspeed": float64(2), + "smartpaste": true, + "softwrap": false, + "splitbottom": true, + "splitright": true, + "statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)", + "statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help", + "statusline": true, + "syntax": true, + "tabmovement": false, + "tabsize": float64(4), + "tabstospaces": false, + "useprimary": true, + "wordwrap": false, } func defaultFileFormat() string { @@ -548,6 +550,22 @@ func validateReload(option string, value interface{}) error { case "prompt", "auto", "disabled": default: return errors.New(option + " must be 'prompt', 'auto' or 'disabled'") + } + + return nil +} + +func validateMatchBraceStyle(option string, value interface{}) error { + val, ok := value.(string) + + if !ok { + errors.New("Expected string type for matchbracestyle") + } + + switch val { + case "underline", "highlight": + default: + return errors.New(option + " must be 'underline' or 'highlight'") } return nil diff --git a/internal/display/bufwindow.go b/internal/display/bufwindow.go index fde20969..bca6bf8e 100644 --- a/internal/display/bufwindow.go +++ b/internal/display/bufwindow.go @@ -407,7 +407,9 @@ func (w *BufWindow) displayBuffer() { if found { matchingBraces = append(matchingBraces, mb) if !left { - matchingBraces = append(matchingBraces, curLoc) + if b.Settings["matchbracestyle"].(string) != "highlight" { + matchingBraces = append(matchingBraces, curLoc) + } } else { matchingBraces = append(matchingBraces, curLoc.Move(-1, b)) } @@ -557,7 +559,15 @@ func (w *BufWindow) displayBuffer() { for _, mb := range matchingBraces { if mb.X == bloc.X && mb.Y == bloc.Y { - style = style.Underline(true) + if b.Settings["matchbracestyle"].(string) == "highlight" { + if s, ok := config.Colorscheme["match-brace"]; ok { + style = s + } else { + style = style.Reverse(true) + } + } else { + style = style.Underline(true) + } } } } diff --git a/runtime/colorschemes/atom-dark.micro b/runtime/colorschemes/atom-dark.micro index 3efd5d4d..d7f8eff6 100644 --- a/runtime/colorschemes/atom-dark.micro +++ b/runtime/colorschemes/atom-dark.micro @@ -28,3 +28,4 @@ color-link color-column "#2D2F31" #No extended types (bool in C, etc.) #color-link type.extended "default" #Plain brackets +color-link match-brace "#1D1F21,#62B1FE" diff --git a/runtime/colorschemes/bubblegum.micro b/runtime/colorschemes/bubblegum.micro index af0dbce6..4c039d4d 100644 --- a/runtime/colorschemes/bubblegum.micro +++ b/runtime/colorschemes/bubblegum.micro @@ -26,3 +26,4 @@ color-link color-column "254" #No extended types (bool in C, &c.) and plain brackets color-link type.extended "241,231" color-link symbol.brackets "241,231" +color-link match-brace "231,28" diff --git a/runtime/colorschemes/cmc-16.micro b/runtime/colorschemes/cmc-16.micro index 44be786d..09ad6eaf 100644 --- a/runtime/colorschemes/cmc-16.micro +++ b/runtime/colorschemes/cmc-16.micro @@ -42,3 +42,4 @@ color-link gutter-warning "red" color-link color-column "cyan" color-link underlined.url "underline blue, white" color-link divider "blue" +color-link match-brace "black,cyan" diff --git a/runtime/colorschemes/cmc-tc.micro b/runtime/colorschemes/cmc-tc.micro index c20e7370..f142210e 100644 --- a/runtime/colorschemes/cmc-tc.micro +++ b/runtime/colorschemes/cmc-tc.micro @@ -38,3 +38,4 @@ color-link color-column "#f26522" color-link constant.bool "bold #55ffff" color-link constant.bool.true "bold #85ff85" color-link constant.bool.false "bold #ff8585" +color-link match-brace "#1e2124,#55ffff" diff --git a/runtime/colorschemes/darcula.micro b/runtime/colorschemes/darcula.micro index f8918160..560ab585 100644 --- a/runtime/colorschemes/darcula.micro +++ b/runtime/colorschemes/darcula.micro @@ -29,3 +29,4 @@ color-link color-column "#2C2C2C" color-link type.extended "default" #color-link symbol.brackets "default" color-link symbol.tag "#AE81FF,#242424" +color-link match-brace "#242424,#7A9EC2" diff --git a/runtime/colorschemes/default.micro b/runtime/colorschemes/default.micro index 27cba2d5..1c3b5eab 100644 --- a/runtime/colorschemes/default.micro +++ b/runtime/colorschemes/default.micro @@ -29,3 +29,4 @@ color-link color-column "#323232" color-link type.extended "default" #color-link symbol.brackets "default" color-link symbol.tag "#AE81FF,#282828" +color-link match-brace "#282828,#AE81FF" diff --git a/runtime/colorschemes/dracula-tc.micro b/runtime/colorschemes/dracula-tc.micro index f87572ff..b242eb6c 100644 --- a/runtime/colorschemes/dracula-tc.micro +++ b/runtime/colorschemes/dracula-tc.micro @@ -43,3 +43,4 @@ color-link cursor-line "#44475A,#F8F8F2" color-link color-column "#44475A" color-link type.extended "default" +color-link match-brace "#282A36,#FF79C6" diff --git a/runtime/colorschemes/dukedark-tc.micro b/runtime/colorschemes/dukedark-tc.micro index 5774468b..54afff60 100644 --- a/runtime/colorschemes/dukedark-tc.micro +++ b/runtime/colorschemes/dukedark-tc.micro @@ -33,3 +33,4 @@ color-link type "bold #3cc83c,#001e28" color-link type.keyword "bold #5aaae6,#001e28" color-link type.extended "#ffffff,#001e28" color-link underlined "#608b4e,#001e28" +color-link match-brace "#001e28,#5aaae6" diff --git a/runtime/colorschemes/dukelight-tc.micro b/runtime/colorschemes/dukelight-tc.micro index d3f492d2..c381f2b1 100644 --- a/runtime/colorschemes/dukelight-tc.micro +++ b/runtime/colorschemes/dukelight-tc.micro @@ -33,3 +33,4 @@ color-link type "bold #004080,#f0f0f0" color-link type.keyword "bold #780050,#f0f0f0" color-link type.extended "#000000,#f0f0f0" color-link underlined "#3f7f5f,#f0f0f0" +color-link match-brace "#f0f0f0,#780050" diff --git a/runtime/colorschemes/dukeubuntu-tc.micro b/runtime/colorschemes/dukeubuntu-tc.micro index 1a14b3f8..7c9f7afb 100644 --- a/runtime/colorschemes/dukeubuntu-tc.micro +++ b/runtime/colorschemes/dukeubuntu-tc.micro @@ -33,3 +33,4 @@ color-link type "bold #3cc83c,#2d0023" color-link type.keyword "bold #5aaae6,#2d0023" color-link type.extended "#ffffff,#2d0023" color-link underlined "#886484,#2d0023" +color-link match-brace "#2d0023,#5aaae6" diff --git a/runtime/colorschemes/geany.micro b/runtime/colorschemes/geany.micro index b6851a20..7333a2a2 100644 --- a/runtime/colorschemes/geany.micro +++ b/runtime/colorschemes/geany.micro @@ -24,3 +24,4 @@ color-link diff-modified "yellow" color-link diff-deleted "red" color-link gutter-error ",red" color-link gutter-warning "red" +color-link match-brace "black,cyan" diff --git a/runtime/colorschemes/gotham.micro b/runtime/colorschemes/gotham.micro index e8dc99c9..600822b6 100644 --- a/runtime/colorschemes/gotham.micro +++ b/runtime/colorschemes/gotham.micro @@ -24,3 +24,4 @@ color-link gutter-warning "#EDB443,#11151C" color-link cursor-line "#091F2E" color-link color-column "#11151C" color-link symbol "#99D1CE,#0C1014" +color-link match-brace "#0C1014,#D26937" diff --git a/runtime/colorschemes/gruvbox-tc.micro b/runtime/colorschemes/gruvbox-tc.micro index e41b1ac2..e6301e67 100644 --- a/runtime/colorschemes/gruvbox-tc.micro +++ b/runtime/colorschemes/gruvbox-tc.micro @@ -24,3 +24,4 @@ color-link cursor-line "#3c3836" color-link color-column "#79740e" color-link statusline "#ebdbb2,#665c54" color-link tabbar "#ebdbb2,#665c54" +color-link match-brace "#282828,#d3869b" diff --git a/runtime/colorschemes/gruvbox.micro b/runtime/colorschemes/gruvbox.micro index 823524ae..a59e99ef 100644 --- a/runtime/colorschemes/gruvbox.micro +++ b/runtime/colorschemes/gruvbox.micro @@ -21,3 +21,4 @@ color-link cursor-line "237" color-link color-column "237" color-link statusline "223,237" color-link tabbar "223,237" +color-link match-brace "235,72" diff --git a/runtime/colorschemes/material-tc.micro b/runtime/colorschemes/material-tc.micro index 7cb30658..561cf81c 100644 --- a/runtime/colorschemes/material-tc.micro +++ b/runtime/colorschemes/material-tc.micro @@ -30,3 +30,4 @@ color-link tabbar "#80DEEA,#3b4d56" color-link todo "bold #C792EA,#263238" color-link type "#FFCB6B,#263238" color-link underlined "underline #EEFFFF,#263238" +color-link match-brace "#263238,#C792EA" diff --git a/runtime/colorschemes/monokai-dark.micro b/runtime/colorschemes/monokai-dark.micro index 3449b305..51174309 100644 --- a/runtime/colorschemes/monokai-dark.micro +++ b/runtime/colorschemes/monokai-dark.micro @@ -23,3 +23,4 @@ color-link gutter-error "#CB4B16" color-link gutter-warning "#E6DB74" color-link cursor-line "#323232" color-link color-column "#323232" +color-link match-brace "#1D0000,#AE81FF" diff --git a/runtime/colorschemes/monokai.micro b/runtime/colorschemes/monokai.micro index 4f93211a..e33cf830 100644 --- a/runtime/colorschemes/monokai.micro +++ b/runtime/colorschemes/monokai.micro @@ -29,3 +29,4 @@ color-link color-column "#323232" color-link type.extended "default" #color-link symbol.brackets "default" color-link symbol.tag "#AE81FF,#282828" +color-link match-brace "#282828,#AE81FF" diff --git a/runtime/colorschemes/one-dark.micro b/runtime/colorschemes/one-dark.micro index c3b0e8cd..b6c96954 100644 --- a/runtime/colorschemes/one-dark.micro +++ b/runtime/colorschemes/one-dark.micro @@ -34,3 +34,4 @@ color-link todo "#8B98AB" color-link type "#66D9EF" color-link type.keyword "#C678DD" color-link underlined "#8996A8" +color-link match-brace "#21252C,#C678DD" diff --git a/runtime/colorschemes/railscast.micro b/runtime/colorschemes/railscast.micro index 3abb1683..61934b94 100644 --- a/runtime/colorschemes/railscast.micro +++ b/runtime/colorschemes/railscast.micro @@ -31,3 +31,5 @@ color-link space "underline #e6e1dc,#2b2b2b" #the Python syntax definition are wrong. This is not how you should do decorators! color-link brightgreen "#edb753,#2b2b2b" + +color-link match-brace "#2b2b2b,#a5c261" diff --git a/runtime/colorschemes/simple.micro b/runtime/colorschemes/simple.micro index 4ee416d4..dc12002e 100644 --- a/runtime/colorschemes/simple.micro +++ b/runtime/colorschemes/simple.micro @@ -27,3 +27,4 @@ color-link type.extended "default" color-link symbol.brackets "default" #Color shebangs the comment color color-link preproc.shebang "comment" +color-link match-brace ",magenta" diff --git a/runtime/colorschemes/solarized-tc.micro b/runtime/colorschemes/solarized-tc.micro index f2840ec3..d68a024a 100644 --- a/runtime/colorschemes/solarized-tc.micro +++ b/runtime/colorschemes/solarized-tc.micro @@ -26,3 +26,4 @@ color-link cursor-line "#003541" color-link color-column "#003541" color-link type.extended "#839496,#002833" color-link symbol.brackets "#839496,#002833" +color-link match-brace "#002833,#268BD2" diff --git a/runtime/colorschemes/solarized.micro b/runtime/colorschemes/solarized.micro index 19b8e2c3..745b46ea 100644 --- a/runtime/colorschemes/solarized.micro +++ b/runtime/colorschemes/solarized.micro @@ -25,3 +25,4 @@ color-link cursor-line "black" color-link color-column "black" color-link type.extended "default" color-link symbol.brackets "default" +color-link match-brace ",blue" diff --git a/runtime/colorschemes/sunny-day.micro b/runtime/colorschemes/sunny-day.micro index f851f318..82e4b8f4 100644 --- a/runtime/colorschemes/sunny-day.micro +++ b/runtime/colorschemes/sunny-day.micro @@ -24,3 +24,4 @@ color-link gutter-warning "88" color-link cursor-line "229" #color-link color-column "196" color-link current-line-number "246" +color-line match-brace "230,22" diff --git a/runtime/colorschemes/twilight.micro b/runtime/colorschemes/twilight.micro index f59d9e41..224fb7fd 100644 --- a/runtime/colorschemes/twilight.micro +++ b/runtime/colorschemes/twilight.micro @@ -35,3 +35,4 @@ color-link todo "#8B98AB" color-link type "#F9EE98" color-link type.keyword "#CDA869" color-link underlined "#8996A8" +color-link match-brace "#141414,#E0C589" diff --git a/runtime/colorschemes/zenburn.micro b/runtime/colorschemes/zenburn.micro index ec8a9580..e4f91175 100644 --- a/runtime/colorschemes/zenburn.micro +++ b/runtime/colorschemes/zenburn.micro @@ -25,3 +25,4 @@ color-link gutter-warning "174,237" color-link cursor-line "238" color-link color-column "238" color-link current-line-number "188,237" +color-link match-brace "237,223" diff --git a/runtime/help/colors.md b/runtime/help/colors.md index ac8b4be3..83800597 100644 --- a/runtime/help/colors.md +++ b/runtime/help/colors.md @@ -194,6 +194,7 @@ Here is a list of the colorscheme groups that you can use: * divider (Color of the divider between vertical splits) * message (Color of messages in the bottom line of the screen) * error-message (Color of error messages in the bottom line of the screen) +* match-brace (Color of matching brackets when `matchbracestyle` is set to `highlight`) Colorschemes must be placed in the `~/.config/micro/colorschemes` directory to be used. diff --git a/runtime/help/options.md b/runtime/help/options.md index 75e877e8..a492bc44 100644 --- a/runtime/help/options.md +++ b/runtime/help/options.md @@ -210,11 +210,19 @@ Here are the available options: default value: `false` -* `matchbrace`: underline matching braces for '()', '{}', '[]' when the cursor - is on a brace character. +* `matchbrace`: show matching braces for '()', '{}', '[]' when the cursor + is on a brace character or next to it. default value: `true` +* `matchbracestyle`: whether to underline or highlight matching braces when + `matchbrace` is enabled. The color of highlight is determined by the `match-brace` + field in the current theme. Possible values: + * `underline`: underline matching braces. + * `highlight`: use `match-brace` style from the current theme. + + default value: `underline` + * `mkparents`: if a file is opened on a path that does not exist, the file cannot be saved because the parent directories don't exist. This option lets micro automatically create the parent directories in such a situation. @@ -495,6 +503,7 @@ so that you can see what the formatting should look like. "linter": true, "literate": true, "matchbrace": true, + "matchbracestyle": "underline", "mkparents": false, "mouse": true, "parsecursor": false, From d2ee6107a3db8cfecdd4f3d7135553dcad69873c Mon Sep 17 00:00:00 2001 From: "Yevhen Babiichuk (DustDFG)" Date: Wed, 13 Mar 2024 21:44:41 +0200 Subject: [PATCH 26/43] Highlight autcompleted command in statusline for simple theme (#3057) Signed-off-by: Yevhen Babiichuk (DustDFG) Co-authored-by: Avi Halachmi (:avih) --- runtime/colorschemes/simple.micro | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/colorschemes/simple.micro b/runtime/colorschemes/simple.micro index dc12002e..74c71e02 100644 --- a/runtime/colorschemes/simple.micro +++ b/runtime/colorschemes/simple.micro @@ -10,6 +10,7 @@ color-link ignore "default" color-link error ",brightred" color-link todo ",brightyellow" color-link hlsearch "black,yellow" +color-link statusline "black,white" color-link indent-char "black" color-link line-number "yellow" color-link current-line-number "red" From bfc4b1d1959914263e9c4488ec5022cc678d8235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:58:44 +0100 Subject: [PATCH 27/43] termwindow: Show cursor only when his X and Y axis is smaller than the window (#3036) --- internal/display/termwindow.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/display/termwindow.go b/internal/display/termwindow.go index 6c30fa7f..e5d80494 100644 --- a/internal/display/termwindow.go +++ b/internal/display/termwindow.go @@ -110,6 +110,8 @@ func (w *TermWindow) Display() { } if w.State.CursorVisible() && w.active { curx, cury := w.State.Cursor() - screen.ShowCursor(curx+w.X, cury+w.Y) + if curx < w.Width && cury < w.Height { + screen.ShowCursor(curx+w.X, cury+w.Y) + } } } From 628d9bb37bff5918989c537f1fafe13dc92b0169 Mon Sep 17 00:00:00 2001 From: Dmytro Maluka Date: Wed, 13 Mar 2024 21:02:11 +0100 Subject: [PATCH 28/43] Fix split pane divider hovering over neighboring split pane (#3070) Fix the following funny issue: if we open 3 vertical split panes (i.e. with 2 vertical dividers between them) and drag the rightmost divider to the left (for resizing the middle and the rightmost split panes), it does not stop at the leftmost divider but jumps over it and then hovers over the leftmost split pane. And likewise with horizontal split panes. --- internal/views/splits.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/views/splits.go b/internal/views/splits.go index 1168ab5d..5fb7dc65 100644 --- a/internal/views/splits.go +++ b/internal/views/splits.go @@ -185,6 +185,9 @@ func (n *Node) hResizeSplit(i int, size int) bool { // ResizeSplit resizes a certain split to a given size func (n *Node) ResizeSplit(size int) bool { + if size <= 0 { + return false + } if len(n.parent.children) <= 1 { // cannot resize a lone node return false From dcdd3e749a4f0a4f1e3608379c6bcf35dfc311ce Mon Sep 17 00:00:00 2001 From: Dmytro Maluka Date: Wed, 13 Mar 2024 21:11:04 +0100 Subject: [PATCH 29/43] Fix ruler overwriting neighboring split pane + fix crash #3052 (#3069) * Fix gutter overwriting other split pane When we resize a split pane to a very small width, so that the gutter does not fit in the pane, it overwrites the sibling split pane. To fix it, clean up the calculation of gutter width, buffer width and scrollbar width, so that they add up exactly to the window width, and ensure that we don't draw the gutter beyond this calculated gutter width (gutterOffset). As a bonus, this also fixes the crash #3052 (observed when resizing a split pane to a very small width, if wordwrap is enabled), by ensuring that bufWidth is never negative. [*] By the gutter we mean of course gutter + diffgutter + ruler. * Don't display line numbers if buffer width is 0 and softwrap is on If softwrap is enabled, the line numbers displayed in the ruler depend on the heights of the displayed softwrapped lines, which depend on the width of the displayed buffer. If this width is 0 (e.g. after resizing buffer pane to a very small width), there is no displayed text at all, so line numbers don't make sense. So don't display line numbers in this case. * Fix buffer text overwriting scrollbar when window width is 1 char --- internal/display/bufwindow.go | 45 ++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/internal/display/bufwindow.go b/internal/display/bufwindow.go index bca6bf8e..6e67f845 100644 --- a/internal/display/bufwindow.go +++ b/internal/display/bufwindow.go @@ -129,6 +129,11 @@ func (w *BufWindow) updateDisplayInfo() { w.bufHeight-- } + scrollbarWidth := 0 + if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height && w.Width > 0 { + scrollbarWidth = 1 + } + w.hasMessage = len(b.Messages) > 0 // We need to know the string length of the largest line number @@ -146,13 +151,13 @@ func (w *BufWindow) updateDisplayInfo() { w.gutterOffset += w.maxLineNumLength + 1 } - prevBufWidth := w.bufWidth - - w.bufWidth = w.Width - w.gutterOffset - if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height { - w.bufWidth-- + if w.gutterOffset > w.Width-scrollbarWidth { + w.gutterOffset = w.Width - scrollbarWidth } + prevBufWidth := w.bufWidth + w.bufWidth = w.Width - w.gutterOffset - scrollbarWidth + if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) { for _, c := range w.Buf.GetCursors() { c.LastVisualX = c.GetVisualX() @@ -277,13 +282,17 @@ func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) { break } } - screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s) - vloc.X++ - screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s) - vloc.X++ + for i := 0; i < 2 && vloc.X < w.gutterOffset; i++ { + screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s) + vloc.X++ + } } func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) { + if vloc.X >= w.gutterOffset { + return + } + symbol := ' ' styleName := "" @@ -319,26 +328,28 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, vloc } else { lineInt = bloc.Y - cursorLine } - lineNum := strconv.Itoa(util.Abs(lineInt)) + lineNum := []rune(strconv.Itoa(util.Abs(lineInt))) // Write the spaces before the line number if necessary - for i := 0; i < w.maxLineNumLength-len(lineNum); i++ { + for i := 0; i < w.maxLineNumLength-len(lineNum) && vloc.X < w.gutterOffset; i++ { screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) vloc.X++ } // Write the actual line number - for _, ch := range lineNum { - if softwrapped { + for i := 0; i < len(lineNum) && vloc.X < w.gutterOffset; i++ { + if softwrapped || (w.bufWidth == 0 && w.Buf.Settings["softwrap"] == true) { screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) } else { - screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle) + screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, lineNum[i], nil, lineNumStyle) } vloc.X++ } // Write the extra space - screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) - vloc.X++ + if vloc.X < w.gutterOffset { + screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle) + vloc.X++ + } } // getStyle returns the highlight style for the given character position @@ -619,7 +630,7 @@ func (w *BufWindow) displayBuffer() { wordwidth := 0 totalwidth := w.StartCol - nColsBeforeStart - for len(line) > 0 { + for len(line) > 0 && vloc.X < maxWidth { r, combc, size := util.DecodeCharacter(line) line = line[size:] From a01ae925417d197a20798d313f2a972b4c142300 Mon Sep 17 00:00:00 2001 From: dimaguy Date: Wed, 13 Mar 2024 20:12:38 +0000 Subject: [PATCH 30/43] Add main tag to html syntax highlighting (#2999) * Add main tag to html syntax highlighting * Reorder main --- runtime/syntax/html.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/syntax/html.yaml b/runtime/syntax/html.yaml index 6386c97e..70c136c8 100644 --- a/runtime/syntax/html.yaml +++ b/runtime/syntax/html.yaml @@ -8,7 +8,7 @@ rules: - preproc: "" # Opening tag - symbol.tag: - start: "<(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h[1-6]|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|section|select|small|source|span|strike|strong|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr)\\b" + start: "<(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h[1-6]|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|main|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|section|select|small|source|span|strike|strong|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr)\\b" end: ">" rules: - identifier: "\\b(placeholder|style|alt|bgcolor|height|href|id|(aria|data)\\-.+|label|longdesc|name|on(click|focus|load|mouseover)|size|span|src|target|type|value|width|class|charset|content|rel|integrity|crossorigin|for|onsubmit|lang|role)\\b" @@ -34,7 +34,7 @@ rules: # Closing tag - symbol.tag: - start: "" rules: # Anything in the closing tag is an error From bd306d67b449a756ee3a89af1c169187edaa7f58 Mon Sep 17 00:00:00 2001 From: Mikko Date: Wed, 13 Mar 2024 22:16:10 +0200 Subject: [PATCH 31/43] Smarter smartpaste (#3001) (#3002) * smarterpaste(?) * make it more readable * fix edge cases * fix paste starting with a single space * fix single line paste --- internal/action/actions.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index ea0255f9..c5310ec2 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -1274,9 +1274,13 @@ func (h *BufPane) PastePrimary() bool { func (h *BufPane) paste(clip string) { if h.Buf.Settings["smartpaste"].(bool) { - if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 { - leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y)) - clip = strings.ReplaceAll(clip, "\n", "\n"+string(leadingWS)) + if h.Cursor.X > 0 { + leadingPasteWS := string(util.GetLeadingWhitespace([]byte(clip))) + if leadingPasteWS != " " && strings.Contains(clip, "\n"+leadingPasteWS) { + leadingWS := string(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) + clip = strings.TrimPrefix(clip, leadingPasteWS) + clip = strings.ReplaceAll(clip, "\n"+leadingPasteWS, "\n"+leadingWS) + } } } From ca3a9d0794a26ae18ad4f49399ff02b1e5be674a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Wed, 13 Mar 2024 21:32:12 +0100 Subject: [PATCH 32/43] command: Add capability to use relative numbers in goto (#2985) * command: Handle relative line numbers for goto * help: Adapt goto command documentation --- internal/action/command.go | 47 +++++++++++++++++++++++++++----------- runtime/help/commands.md | 8 ++++--- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/internal/action/command.go b/internal/action/command.go index 16d2fff8..fb1eadfb 100644 --- a/internal/action/command.go +++ b/internal/action/command.go @@ -697,6 +697,34 @@ func (h *BufPane) QuitCmd(args []string) { h.Quit() } +func convertLine(h *BufPane, line string) (int, error) { + lineNum := 0 + var err error + + // Check for special negative movement beginning from the end of the file + if strings.HasPrefix(line, "~") { + lineNum, err = strconv.Atoi(line[1:]) + if err != nil { + return 0, err + } + lineNum = h.Buf.LinesNum() + 1 - lineNum + } else { + lineNum, err = strconv.Atoi(line) + if err != nil { + return 0, err + } + + // Check for relative numbers + if strings.HasPrefix(line, "-") || strings.HasPrefix(line, "+") { + lineNum = h.Buf.GetActiveCursor().Y + 1 + lineNum + } + } + + lineNum = util.Clamp(lineNum-1, 0, h.Buf.LinesNum()-1) + + return lineNum, err +} + // GotoCmd is a command that will send the cursor to a certain // position in the buffer // For example: `goto line`, or `goto line:col` @@ -704,37 +732,30 @@ func (h *BufPane) GotoCmd(args []string) { if len(args) <= 0 { InfoBar.Error("Not enough arguments") } else { + line, col := 0, 0 + var err error h.RemoveAllMultiCursors() if strings.Contains(args[0], ":") { parts := strings.SplitN(args[0], ":", 2) - line, err := strconv.Atoi(parts[0]) + line, err = convertLine(h, parts[0]) if err != nil { InfoBar.Error(err) return } - col, err := strconv.Atoi(parts[1]) + col, err = strconv.Atoi(parts[1]) if err != nil { InfoBar.Error(err) return } - if line < 0 { - line = h.Buf.LinesNum() + 1 + line - } - line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1) col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line))) - h.GotoLoc(buffer.Loc{col, line}) } else { - line, err := strconv.Atoi(args[0]) + line, err = convertLine(h, args[0]) if err != nil { InfoBar.Error(err) return } - if line < 0 { - line = h.Buf.LinesNum() + 1 + line - } - line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1) - h.GotoLoc(buffer.Loc{0, line}) } + h.GotoLoc(buffer.Loc{col, line}) } } diff --git a/runtime/help/commands.md b/runtime/help/commands.md index afcffd29..e8630310 100644 --- a/runtime/help/commands.md +++ b/runtime/help/commands.md @@ -31,9 +31,11 @@ quotes here but these are not necessary when entering the command in micro. * `quit`: quits micro. -* `goto 'line'`: jumps to the given line number. A negative number can be - passed to jump inward from the end of the file; for example, -5 jumps - to the 5th-last line in the file. +* `goto 'line[:col]'`: Jumps to the given line (and optional column) number. + `line` can be prefixed with `+`, `-` or `~`. + +/- will perform relative jumps from the current position of the cursor + (e.g. +5 will jump 5 lines down), while ~ will perform a jump inward + from the end of the file (e.g. ~5 jumps to the 5th-last line in the file). * `replace 'search' 'value' 'flags'?`: This will replace `search` with `value`. The `flags` are optional. Possible flags are: From 8368af3cc8e5cc3badf9c2688d8d578ed9a8a0c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Wed, 13 Mar 2024 21:34:52 +0100 Subject: [PATCH 33/43] command: Fix set local-only options for the current buffer only (#3042) --- internal/action/command.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/action/command.go b/internal/action/command.go index fb1eadfb..cc20d649 100644 --- a/internal/action/command.go +++ b/internal/action/command.go @@ -532,8 +532,12 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error { } } - for _, b := range buffer.OpenBuffers { - b.SetOptionNative(option, nativeValue) + if local { + MainTab().CurPane().Buf.SetOptionNative(option, nativeValue) + } else { + for _, b := range buffer.OpenBuffers { + b.SetOptionNative(option, nativeValue) + } } return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json")) From 64370b70d6d6bcf46be424ec78bdb85aa591d6e5 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Tue, 20 Oct 2020 22:10:41 +0200 Subject: [PATCH 34/43] Highlighting tab errors Added option `hltaberrors` which helps to spot sloppy whitespace errors with tabs used instead of spaces or vice versa. It uses the value of `tabstospaces` option as a criterion whether a tab or space character is an error or not. If `tabstospaces` is on, we probably expect that the file should contain no tab characters, so any tab character is highlighted as an error. If `tabstospaces` is off, we probably expect that the file uses indentation with tabs, so space characters in the initial indent part of lines are highlighted as errors. --- internal/config/settings.go | 1 + internal/display/bufwindow.go | 14 ++++++++++++++ runtime/help/options.md | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/internal/config/settings.go b/internal/config/settings.go index 3b9bfaef..40e271b7 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -289,6 +289,7 @@ var defaultCommonSettings = map[string]interface{}{ "fileformat": defaultFileFormat(), "filetype": "unknown", "hlsearch": false, + "hltaberrors": false, "incsearch": true, "ignorecase": true, "indentchar": " ", diff --git a/internal/display/bufwindow.go b/internal/display/bufwindow.go index 6e67f845..8d4645e0 100644 --- a/internal/display/bufwindow.go +++ b/internal/display/bufwindow.go @@ -495,6 +495,8 @@ func (w *BufWindow) displayBuffer() { vloc.X = w.gutterOffset } + leadingwsEnd := len(util.GetLeadingWhitespace(b.LineBytes(bloc.Y))) + line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y) if startStyle != nil { curStyle = *startStyle @@ -518,6 +520,18 @@ func (w *BufWindow) displayBuffer() { // over cursor-line and color-column dontOverrideBackground := origBg != defBg + if b.Settings["hltaberrors"].(bool) { + if s, ok := config.Colorscheme["tab-error"]; ok { + isTab := (r == '\t') || (r == ' ' && !showcursor) + if (b.Settings["tabstospaces"].(bool) && isTab) || + (!b.Settings["tabstospaces"].(bool) && bloc.X < leadingwsEnd && r == ' ' && !isTab) { + fg, _, _ := s.Decompose() + style = style.Background(fg) + dontOverrideBackground = true + } + } + } + for _, c := range cursors { if c.HasSelection() && (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) || diff --git a/runtime/help/options.md b/runtime/help/options.md index a492bc44..3826f27b 100644 --- a/runtime/help/options.md +++ b/runtime/help/options.md @@ -174,6 +174,13 @@ Here are the available options: default value: `false` +* `hltaberrors`: highlight tabs when spaces are expected, and spaces when tabs + are expected. More precisely: if `tabstospaces` option is on, highlight + all tab characters; if `tabstospaces` is off, highlight space characters + in the initial indent part of the line. + + default value: `false` + * `incsearch`: enable incremental search in "Find" prompt (matching as you type). default value: `true` From 104caf08dd9ee1bd4be324ff9867b629aca3a08a Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Tue, 20 Oct 2020 22:52:49 +0200 Subject: [PATCH 35/43] Highlighting trailing whitespaces Added option `hltrailingws` for highlighting trailing whitespaces at the end of lines. Note that it behaves in a "smart" way. It doesn't highlight newly added (transient) trailing whitespaces that naturally occur while typing text. It would be annoying to see transient highlighting every time we enter a space at the end of a line while typing. So a newly added trailing whitespace starts being highlighting only after the cursor moves to another line. Thus the highlighting serves its purpose: it draws our attention to annoying sloppy forgotten trailing whitespaces. --- internal/action/bufpane.go | 7 +++++ internal/buffer/cursor.go | 7 +++++ internal/buffer/eventhandler.go | 51 +++++++++++++++++++++++++++++++++ internal/config/settings.go | 1 + internal/display/bufwindow.go | 25 +++++++++++++++- internal/util/util.go | 23 +++++++++++++++ runtime/help/options.md | 6 ++++ 7 files changed, 119 insertions(+), 1 deletion(-) diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index dea7b906..89d174c7 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -509,6 +509,13 @@ func (h *BufPane) HandleEvent(event tcell.Event) { InfoBar.ClearGutter() } } + + cursors := h.Buf.GetCursors() + for _, c := range cursors { + if c.NewTrailingWsY != c.Y { + c.NewTrailingWsY = -1 + } + } } // Bindings returns the current bindings tree for this buffer. diff --git a/internal/buffer/cursor.go b/internal/buffer/cursor.go index 12fc5db2..bd3ae068 100644 --- a/internal/buffer/cursor.go +++ b/internal/buffer/cursor.go @@ -30,6 +30,11 @@ type Cursor struct { // to know what the original selection was OrigSelection [2]Loc + // The line number where a new trailing whitespace has been added + // or -1 if there is no new trailing whitespace at this cursor. + // This is used for checking if a trailing whitespace should be highlighted + NewTrailingWsY int + // Which cursor index is this (for multiple cursors) Num int } @@ -38,6 +43,8 @@ func NewCursor(b *Buffer, l Loc) *Cursor { c := &Cursor{ buf: b, Loc: l, + + NewTrailingWsY: -1, } c.StoreVisualX() return c diff --git a/internal/buffer/eventhandler.go b/internal/buffer/eventhandler.go index 6be34bce..66c44dba 100644 --- a/internal/buffer/eventhandler.go +++ b/internal/buffer/eventhandler.go @@ -106,6 +106,8 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) { c.Relocate() c.LastVisualX = c.GetVisualX() } + + eh.updateTrailingWs(t) } // ExecuteTextEvent runs a text event @@ -342,3 +344,52 @@ func (eh *EventHandler) RedoOneEvent() { eh.UndoStack.Push(t) } + +// updateTrailingWs updates the cursor's trailing whitespace status after a text event +func (eh *EventHandler) updateTrailingWs(t *TextEvent) { + if len(t.Deltas) != 1 { + return + } + text := t.Deltas[0].Text + start := t.Deltas[0].Start + end := t.Deltas[0].End + + c := eh.cursors[eh.active] + isEol := func(loc Loc) bool { + return loc.X == util.CharacterCount(eh.buf.LineBytes(loc.Y)) + } + if t.EventType == TextEventInsert && c.Loc == end && isEol(end) { + var addedTrailingWs bool + addedAfterWs := false + addedWsOnly := false + if start.Y == end.Y { + addedTrailingWs = util.HasTrailingWhitespace(text) + addedWsOnly = util.IsBytesWhitespace(text) + addedAfterWs = start.X > 0 && util.IsWhitespace(c.buf.RuneAt(Loc{start.X - 1, start.Y})) + } else { + lastnl := bytes.LastIndex(text, []byte{'\n'}) + addedTrailingWs = util.HasTrailingWhitespace(text[lastnl+1:]) + } + + if addedTrailingWs && !(addedAfterWs && addedWsOnly) { + c.NewTrailingWsY = c.Y + } else if !addedTrailingWs { + c.NewTrailingWsY = -1 + } + } else if t.EventType == TextEventRemove && c.Loc == start && isEol(start) { + removedAfterWs := util.HasTrailingWhitespace(eh.buf.LineBytes(start.Y)) + var removedWsOnly bool + if start.Y == end.Y { + removedWsOnly = util.IsBytesWhitespace(text) + } else { + firstnl := bytes.Index(text, []byte{'\n'}) + removedWsOnly = util.IsBytesWhitespace(text[:firstnl]) + } + + if removedAfterWs && !removedWsOnly { + c.NewTrailingWsY = c.Y + } else if !removedAfterWs { + c.NewTrailingWsY = -1 + } + } +} diff --git a/internal/config/settings.go b/internal/config/settings.go index 40e271b7..bfb1061f 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -290,6 +290,7 @@ var defaultCommonSettings = map[string]interface{}{ "filetype": "unknown", "hlsearch": false, "hltaberrors": false, + "hltrailingws": false, "incsearch": true, "ignorecase": true, "indentchar": " ", diff --git a/internal/display/bufwindow.go b/internal/display/bufwindow.go index 8d4645e0..942dd167 100644 --- a/internal/display/bufwindow.go +++ b/internal/display/bufwindow.go @@ -495,7 +495,11 @@ func (w *BufWindow) displayBuffer() { vloc.X = w.gutterOffset } - leadingwsEnd := len(util.GetLeadingWhitespace(b.LineBytes(bloc.Y))) + bline := b.LineBytes(bloc.Y) + blineLen := util.CharacterCount(bline) + + leadingwsEnd := len(util.GetLeadingWhitespace(bline)) + trailingwsStart := blineLen - util.CharacterCount(util.GetTrailingWhitespace(bline)) line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y) if startStyle != nil { @@ -532,6 +536,25 @@ func (w *BufWindow) displayBuffer() { } } + if b.Settings["hltrailingws"].(bool) { + if s, ok := config.Colorscheme["trailingws"]; ok { + if bloc.X >= trailingwsStart && bloc.X < blineLen { + hl := true + for _, c := range cursors { + if c.NewTrailingWsY == bloc.Y { + hl = false + break + } + } + if hl { + fg, _, _ := s.Decompose() + style = style.Background(fg) + dontOverrideBackground = true + } + } + } + } + for _, c := range cursors { if c.HasSelection() && (bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) || diff --git a/internal/util/util.go b/internal/util/util.go index fb21c487..bebd949b 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -16,6 +16,7 @@ import ( "strings" "time" "unicode" + "unicode/utf8" "github.com/blang/semver" runewidth "github.com/mattn/go-runewidth" @@ -363,6 +364,28 @@ func GetLeadingWhitespace(b []byte) []byte { return ws } +// GetTrailingWhitespace returns the trailing whitespace of the given byte array +func GetTrailingWhitespace(b []byte) []byte { + ws := []byte{} + for len(b) > 0 { + r, size := utf8.DecodeLastRune(b) + if IsWhitespace(r) { + ws = append([]byte(string(r)), ws...) + } else { + break + } + + b = b[:len(b)-size] + } + return ws +} + +// HasTrailingWhitespace returns true if the given byte array ends with a whitespace +func HasTrailingWhitespace(b []byte) bool { + r, _ := utf8.DecodeLastRune(b) + return IsWhitespace(r) +} + // IntOpt turns a float64 setting to an int func IntOpt(opt interface{}) int { return int(opt.(float64)) diff --git a/runtime/help/options.md b/runtime/help/options.md index 3826f27b..72075820 100644 --- a/runtime/help/options.md +++ b/runtime/help/options.md @@ -181,6 +181,12 @@ Here are the available options: default value: `false` +* `hltrailingws`: highlight trailing whitespaces at ends of lines. Note that + it doesn't highlight newly added trailing whitespaces that naturally occur + while typing text. It highlights only nasty forgotten trailing whitespaces. + + default value: `false` + * `incsearch`: enable incremental search in "Find" prompt (matching as you type). default value: `true` From c52ccad14b3736b2975835351cdba31e9f7c6ea1 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Tue, 20 Oct 2020 23:46:55 +0200 Subject: [PATCH 36/43] hltrailingws: adjust autoclose plugin implementation Fix unwanted highlighting of whitespace in the new line when inserting a newline after a bracket (when hltrailingws is on). To fix it, change the order of operations: insert the new empty line after all other things, to avoid moving the cursor between lines after that. --- runtime/plugins/autoclose/autoclose.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/plugins/autoclose/autoclose.lua b/runtime/plugins/autoclose/autoclose.lua index 531b7601..f1fc2fad 100644 --- a/runtime/plugins/autoclose/autoclose.lua +++ b/runtime/plugins/autoclose/autoclose.lua @@ -50,11 +50,11 @@ function preInsertNewline(bp) for i = 1, #autoNewlinePairs do if curRune == charAt(autoNewlinePairs[i], 1) then if nextRune == charAt(autoNewlinePairs[i], 2) then - bp:InsertNewline() - bp:InsertTab() bp.Buf:Insert(-bp.Cursor.Loc, "\n" .. ws) bp:StartOfLine() bp:CursorLeft() + bp:InsertNewline() + bp:InsertTab() return false end end From b824e767d6f04fa633f341e46a61e692a1c36a79 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Wed, 21 Oct 2020 00:33:18 +0200 Subject: [PATCH 37/43] Add tab-error and trailingws colors to colorschemes --- runtime/colorschemes/atom-dark.micro | 2 ++ runtime/colorschemes/bubblegum.micro | 2 ++ runtime/colorschemes/cmc-16.micro | 2 ++ runtime/colorschemes/cmc-tc.micro | 2 ++ runtime/colorschemes/darcula.micro | 2 ++ runtime/colorschemes/default.micro | 2 ++ runtime/colorschemes/dracula-tc.micro | 3 +++ runtime/colorschemes/dukedark-tc.micro | 2 ++ runtime/colorschemes/dukelight-tc.micro | 2 ++ runtime/colorschemes/dukeubuntu-tc.micro | 2 ++ runtime/colorschemes/geany.micro | 2 ++ runtime/colorschemes/gotham.micro | 2 ++ runtime/colorschemes/gruvbox-tc.micro | 2 ++ runtime/colorschemes/gruvbox.micro | 2 ++ runtime/colorschemes/material-tc.micro | 2 ++ runtime/colorschemes/monokai-dark.micro | 2 ++ runtime/colorschemes/monokai.micro | 2 ++ runtime/colorschemes/one-dark.micro | 2 ++ runtime/colorschemes/railscast.micro | 2 ++ runtime/colorschemes/simple.micro | 2 ++ runtime/colorschemes/solarized-tc.micro | 2 ++ runtime/colorschemes/solarized.micro | 2 ++ runtime/colorschemes/sunny-day.micro | 2 ++ runtime/colorschemes/twilight.micro | 2 ++ runtime/colorschemes/zenburn.micro | 2 ++ 25 files changed, 51 insertions(+) diff --git a/runtime/colorschemes/atom-dark.micro b/runtime/colorschemes/atom-dark.micro index d7f8eff6..0f462995 100644 --- a/runtime/colorschemes/atom-dark.micro +++ b/runtime/colorschemes/atom-dark.micro @@ -29,3 +29,5 @@ color-link color-column "#2D2F31" #color-link type.extended "default" #Plain brackets color-link match-brace "#1D1F21,#62B1FE" +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/bubblegum.micro b/runtime/colorschemes/bubblegum.micro index 4c039d4d..dcc2276d 100644 --- a/runtime/colorschemes/bubblegum.micro +++ b/runtime/colorschemes/bubblegum.micro @@ -27,3 +27,5 @@ color-link color-column "254" color-link type.extended "241,231" color-link symbol.brackets "241,231" color-link match-brace "231,28" +color-link tab-error "210" +color-link trailingws "210" diff --git a/runtime/colorschemes/cmc-16.micro b/runtime/colorschemes/cmc-16.micro index 09ad6eaf..0a50096c 100644 --- a/runtime/colorschemes/cmc-16.micro +++ b/runtime/colorschemes/cmc-16.micro @@ -43,3 +43,5 @@ color-link color-column "cyan" color-link underlined.url "underline blue, white" color-link divider "blue" color-link match-brace "black,cyan" +color-link tab-error "brightred" +color-link trailingws "brightred" diff --git a/runtime/colorschemes/cmc-tc.micro b/runtime/colorschemes/cmc-tc.micro index f142210e..b0502c6f 100644 --- a/runtime/colorschemes/cmc-tc.micro +++ b/runtime/colorschemes/cmc-tc.micro @@ -39,3 +39,5 @@ color-link constant.bool "bold #55ffff" color-link constant.bool.true "bold #85ff85" color-link constant.bool.false "bold #ff8585" color-link match-brace "#1e2124,#55ffff" +color-link tab-error "#d75f5f" +color-link trailingws "#d75f5f" diff --git a/runtime/colorschemes/darcula.micro b/runtime/colorschemes/darcula.micro index 560ab585..7103e842 100644 --- a/runtime/colorschemes/darcula.micro +++ b/runtime/colorschemes/darcula.micro @@ -30,3 +30,5 @@ color-link type.extended "default" #color-link symbol.brackets "default" color-link symbol.tag "#AE81FF,#242424" color-link match-brace "#242424,#7A9EC2" +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/default.micro b/runtime/colorschemes/default.micro index 1c3b5eab..9ae5bfd6 100644 --- a/runtime/colorschemes/default.micro +++ b/runtime/colorschemes/default.micro @@ -30,3 +30,5 @@ color-link type.extended "default" #color-link symbol.brackets "default" color-link symbol.tag "#AE81FF,#282828" color-link match-brace "#282828,#AE81FF" +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/dracula-tc.micro b/runtime/colorschemes/dracula-tc.micro index b242eb6c..af0509c7 100644 --- a/runtime/colorschemes/dracula-tc.micro +++ b/runtime/colorschemes/dracula-tc.micro @@ -44,3 +44,6 @@ color-link color-column "#44475A" color-link type.extended "default" color-link match-brace "#282A36,#FF79C6" + +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/dukedark-tc.micro b/runtime/colorschemes/dukedark-tc.micro index 54afff60..52ec8476 100644 --- a/runtime/colorschemes/dukedark-tc.micro +++ b/runtime/colorschemes/dukedark-tc.micro @@ -34,3 +34,5 @@ color-link type.keyword "bold #5aaae6,#001e28" color-link type.extended "#ffffff,#001e28" color-link underlined "#608b4e,#001e28" color-link match-brace "#001e28,#5aaae6" +color-link tab-error "#d75f5f" +color-link trailingws "#d75f5f" diff --git a/runtime/colorschemes/dukelight-tc.micro b/runtime/colorschemes/dukelight-tc.micro index c381f2b1..c694ffb6 100644 --- a/runtime/colorschemes/dukelight-tc.micro +++ b/runtime/colorschemes/dukelight-tc.micro @@ -34,3 +34,5 @@ color-link type.keyword "bold #780050,#f0f0f0" color-link type.extended "#000000,#f0f0f0" color-link underlined "#3f7f5f,#f0f0f0" color-link match-brace "#f0f0f0,#780050" +color-link tab-error "#ff8787" +color-link trailingws "#ff8787" diff --git a/runtime/colorschemes/dukeubuntu-tc.micro b/runtime/colorschemes/dukeubuntu-tc.micro index 7c9f7afb..b34cc2c4 100644 --- a/runtime/colorschemes/dukeubuntu-tc.micro +++ b/runtime/colorschemes/dukeubuntu-tc.micro @@ -34,3 +34,5 @@ color-link type.keyword "bold #5aaae6,#2d0023" color-link type.extended "#ffffff,#2d0023" color-link underlined "#886484,#2d0023" color-link match-brace "#2d0023,#5aaae6" +color-link tab-error "#d75f5f" +color-link trailingws "#d75f5f" diff --git a/runtime/colorschemes/geany.micro b/runtime/colorschemes/geany.micro index 7333a2a2..43ced31a 100644 --- a/runtime/colorschemes/geany.micro +++ b/runtime/colorschemes/geany.micro @@ -25,3 +25,5 @@ color-link diff-deleted "red" color-link gutter-error ",red" color-link gutter-warning "red" color-link match-brace "black,cyan" +color-link tab-error "brightred" +color-link trailingws "brightred" diff --git a/runtime/colorschemes/gotham.micro b/runtime/colorschemes/gotham.micro index 600822b6..d067a81c 100644 --- a/runtime/colorschemes/gotham.micro +++ b/runtime/colorschemes/gotham.micro @@ -25,3 +25,5 @@ color-link cursor-line "#091F2E" color-link color-column "#11151C" color-link symbol "#99D1CE,#0C1014" color-link match-brace "#0C1014,#D26937" +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/gruvbox-tc.micro b/runtime/colorschemes/gruvbox-tc.micro index e6301e67..65d72b15 100644 --- a/runtime/colorschemes/gruvbox-tc.micro +++ b/runtime/colorschemes/gruvbox-tc.micro @@ -25,3 +25,5 @@ color-link color-column "#79740e" color-link statusline "#ebdbb2,#665c54" color-link tabbar "#ebdbb2,#665c54" color-link match-brace "#282828,#d3869b" +color-link tab-error "#d75f5f" +color-link trailingws "#d75f5f" diff --git a/runtime/colorschemes/gruvbox.micro b/runtime/colorschemes/gruvbox.micro index a59e99ef..46cc09cf 100644 --- a/runtime/colorschemes/gruvbox.micro +++ b/runtime/colorschemes/gruvbox.micro @@ -22,3 +22,5 @@ color-link color-column "237" color-link statusline "223,237" color-link tabbar "223,237" color-link match-brace "235,72" +color-link tab-error "167" +color-link trailingws "167" diff --git a/runtime/colorschemes/material-tc.micro b/runtime/colorschemes/material-tc.micro index 561cf81c..5a7f9c89 100644 --- a/runtime/colorschemes/material-tc.micro +++ b/runtime/colorschemes/material-tc.micro @@ -31,3 +31,5 @@ color-link todo "bold #C792EA,#263238" color-link type "#FFCB6B,#263238" color-link underlined "underline #EEFFFF,#263238" color-link match-brace "#263238,#C792EA" +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/monokai-dark.micro b/runtime/colorschemes/monokai-dark.micro index 51174309..3a1e89c4 100644 --- a/runtime/colorschemes/monokai-dark.micro +++ b/runtime/colorschemes/monokai-dark.micro @@ -24,3 +24,5 @@ color-link gutter-warning "#E6DB74" color-link cursor-line "#323232" color-link color-column "#323232" color-link match-brace "#1D0000,#AE81FF" +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/monokai.micro b/runtime/colorschemes/monokai.micro index e33cf830..13c44b74 100644 --- a/runtime/colorschemes/monokai.micro +++ b/runtime/colorschemes/monokai.micro @@ -30,3 +30,5 @@ color-link type.extended "default" #color-link symbol.brackets "default" color-link symbol.tag "#AE81FF,#282828" color-link match-brace "#282828,#AE81FF" +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/one-dark.micro b/runtime/colorschemes/one-dark.micro index b6c96954..ed994321 100644 --- a/runtime/colorschemes/one-dark.micro +++ b/runtime/colorschemes/one-dark.micro @@ -35,3 +35,5 @@ color-link type "#66D9EF" color-link type.keyword "#C678DD" color-link underlined "#8996A8" color-link match-brace "#21252C,#C678DD" +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/railscast.micro b/runtime/colorschemes/railscast.micro index 61934b94..01c0055d 100644 --- a/runtime/colorschemes/railscast.micro +++ b/runtime/colorschemes/railscast.micro @@ -28,6 +28,8 @@ color-link tabbar "bold #b1b1b1,#232323" color-link cursor-line "#353535" color-link color-column "#353535" color-link space "underline #e6e1dc,#2b2b2b" +color-link tab-error "#d75f5f" +color-link trailingws "#d75f5f" #the Python syntax definition are wrong. This is not how you should do decorators! color-link brightgreen "#edb753,#2b2b2b" diff --git a/runtime/colorschemes/simple.micro b/runtime/colorschemes/simple.micro index 74c71e02..707c04cb 100644 --- a/runtime/colorschemes/simple.micro +++ b/runtime/colorschemes/simple.micro @@ -29,3 +29,5 @@ color-link symbol.brackets "default" #Color shebangs the comment color color-link preproc.shebang "comment" color-link match-brace ",magenta" +color-link tab-error "brightred" +color-link trailingws "brightred" diff --git a/runtime/colorschemes/solarized-tc.micro b/runtime/colorschemes/solarized-tc.micro index d68a024a..8eae0391 100644 --- a/runtime/colorschemes/solarized-tc.micro +++ b/runtime/colorschemes/solarized-tc.micro @@ -27,3 +27,5 @@ color-link color-column "#003541" color-link type.extended "#839496,#002833" color-link symbol.brackets "#839496,#002833" color-link match-brace "#002833,#268BD2" +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/solarized.micro b/runtime/colorschemes/solarized.micro index 745b46ea..4d3923ad 100644 --- a/runtime/colorschemes/solarized.micro +++ b/runtime/colorschemes/solarized.micro @@ -26,3 +26,5 @@ color-link color-column "black" color-link type.extended "default" color-link symbol.brackets "default" color-link match-brace ",blue" +color-link tab-error "brightred" +color-link trailingws "brightred" diff --git a/runtime/colorschemes/sunny-day.micro b/runtime/colorschemes/sunny-day.micro index 82e4b8f4..c4161bce 100644 --- a/runtime/colorschemes/sunny-day.micro +++ b/runtime/colorschemes/sunny-day.micro @@ -25,3 +25,5 @@ color-link cursor-line "229" #color-link color-column "196" color-link current-line-number "246" color-line match-brace "230,22" +color-link tab-error "210" +color-link trailingws "210" diff --git a/runtime/colorschemes/twilight.micro b/runtime/colorschemes/twilight.micro index 224fb7fd..8135bb80 100644 --- a/runtime/colorschemes/twilight.micro +++ b/runtime/colorschemes/twilight.micro @@ -36,3 +36,5 @@ color-link type "#F9EE98" color-link type.keyword "#CDA869" color-link underlined "#8996A8" color-link match-brace "#141414,#E0C589" +color-link tab-error "#D75F5F" +color-link trailingws "#D75F5F" diff --git a/runtime/colorschemes/zenburn.micro b/runtime/colorschemes/zenburn.micro index e4f91175..acbd83fb 100644 --- a/runtime/colorschemes/zenburn.micro +++ b/runtime/colorschemes/zenburn.micro @@ -26,3 +26,5 @@ color-link cursor-line "238" color-link color-column "238" color-link current-line-number "188,237" color-link match-brace "237,223" +color-link tab-error "167" +color-link trailingws "167" From f108c906434b0c5eb3e2159f877d39a97853f078 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Thu, 22 Oct 2020 22:29:16 +0200 Subject: [PATCH 38/43] hltrailingws: improve updateTrailingWs logic Handle the case when the cursor itself hasn't really moved to another line, but its line number has changed due to insert or remove of some lines above. In this case, if the cursor is still at its new trailingws, we should not reset NewTrailingWsY to -1 but update it to the new line number. A scenario exemplifying this issue: Bind some key, e.g. Alt-r, to such a lua function: function insertNewlineAbove(bp) bp.Buf:Insert(buffer.Loc(0, bp.Cursor.Y), "\n") end Then in a file containing these lines: aaa bbb ccc insert a space at the end of bbb line, and then press Alt-r. bbb and ccc are moved one line down, but also the trailing space after bbb becomes highlighted, which isn't what we expect. This commit fixes that. --- internal/buffer/eventhandler.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/buffer/eventhandler.go b/internal/buffer/eventhandler.go index 66c44dba..6dfb32a0 100644 --- a/internal/buffer/eventhandler.go +++ b/internal/buffer/eventhandler.go @@ -391,5 +391,11 @@ func (eh *EventHandler) updateTrailingWs(t *TextEvent) { } else if !removedAfterWs { c.NewTrailingWsY = -1 } + } else if c.NewTrailingWsY != -1 && start.Y != end.Y && c.Loc.GreaterThan(start) && + ((t.EventType == TextEventInsert && c.Y == c.NewTrailingWsY+(end.Y-start.Y)) || + (t.EventType == TextEventRemove && c.Y == c.NewTrailingWsY-(end.Y-start.Y))) { + // The cursor still has its new trailingws + // but its line number was shifted by insert or remove of lines above + c.NewTrailingWsY = c.Y } } From 53efce72fa4bb883e9892d64be3eaab2a1e54d9e Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Thu, 22 Oct 2020 22:54:46 +0200 Subject: [PATCH 39/43] hltrailingws: improve behavior with selection Improve user experience: if we are at a line with a new (i.e. not highlighted yet) trailingws and we begin selecting text, don't highlight the trailingws until we are done with selection, even if we moved the cursor to another line while selecting. --- internal/action/bufpane.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index 89d174c7..2baf6739 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -512,7 +512,8 @@ func (h *BufPane) HandleEvent(event tcell.Event) { cursors := h.Buf.GetCursors() for _, c := range cursors { - if c.NewTrailingWsY != c.Y { + if c.NewTrailingWsY != c.Y && (!c.HasSelection() || + (c.NewTrailingWsY != c.CurSelection[0].Y && c.NewTrailingWsY != c.CurSelection[1].Y)) { c.NewTrailingWsY = -1 } } From 13d1407f60a12b2c450074887bc821f64350a635 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Fri, 23 Oct 2020 00:17:22 +0200 Subject: [PATCH 40/43] hltrailingws: simpler and better undo/redo handling --- internal/buffer/eventhandler.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/buffer/eventhandler.go b/internal/buffer/eventhandler.go index 6dfb32a0..53f64025 100644 --- a/internal/buffer/eventhandler.go +++ b/internal/buffer/eventhandler.go @@ -107,7 +107,9 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) { c.LastVisualX = c.GetVisualX() } - eh.updateTrailingWs(t) + if useUndo { + eh.updateTrailingWs(t) + } } // ExecuteTextEvent runs a text event @@ -292,6 +294,7 @@ func (eh *EventHandler) UndoOneEvent() { if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) { t.C = *eh.cursors[teCursor.Num] eh.cursors[teCursor.Num].Goto(teCursor) + eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY } else { teCursor.Num = -1 } @@ -335,6 +338,7 @@ func (eh *EventHandler) RedoOneEvent() { if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) { t.C = *eh.cursors[teCursor.Num] eh.cursors[teCursor.Num].Goto(teCursor) + eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY } else { teCursor.Num = -1 } From 6dc3df646b57207a28e18213261a864543f18056 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Sun, 27 Nov 2022 21:30:16 +0100 Subject: [PATCH 41/43] readme: Mention hltrailingws/hltaberrors feature --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6d4a8579..3be5f9b3 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ You can also check out the website for Micro at https://micro-editor.github.io. - Small and simple. - Easily configurable. - Macros. +- Smart highlighting of trailing whitespace and tab vs space errors. - Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, … ## Installation From c4c5b184c21f282b027171dd4d4c738a3fd3745b Mon Sep 17 00:00:00 2001 From: Dmytro Maluka Date: Thu, 14 Mar 2024 03:52:52 +0100 Subject: [PATCH 42/43] Improve support for mouse events handling (#2605) - If a mouse event is bound to a Lua function, pass *tcell.EventMouse to this Lua function, so that it can find out the position where a button was clicked etc, just like the built-in MousePress and MouseMultiCursor actions. - Make mouse actions more a first-class citizen: allow chaining them and running onAction and preAction callbacks for them, just like key actions. --- internal/action/actions.go | 2 +- internal/action/bufpane.go | 97 ++++++++++++++++++++++---------------- 2 files changed, 58 insertions(+), 41 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index c5310ec2..e6cd1a5d 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -1716,7 +1716,7 @@ func (h *BufPane) PlayMacro() bool { switch t := action.(type) { case rune: h.DoRuneInsert(t) - case func(*BufPane) bool: + case BufKeyAction: t(h) } } diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index 2baf6739..1a9b14f8 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -17,6 +17,8 @@ import ( "github.com/zyedidia/tcell/v2" ) +type BufAction interface{} + // BufKeyAction represents an action bound to a key. type BufKeyAction func(*BufPane) bool @@ -44,8 +46,9 @@ func init() { BufBindings = NewKeyTree() } -// LuaAction makes a BufKeyAction from a lua function. -func LuaAction(fn string) func(*BufPane) bool { +// LuaAction makes an action from a lua function. It returns either a BufKeyAction +// or a BufMouseAction depending on the event type. +func LuaAction(fn string, k Event) BufAction { luaFn := strings.Split(fn, ".") if len(luaFn) <= 1 { return nil @@ -55,33 +58,42 @@ func LuaAction(fn string) func(*BufPane) bool { if pl == nil { return nil } - return func(h *BufPane) bool { - val, err := pl.Call(plFn, luar.New(ulua.L, h)) - if err != nil { - screen.TermMessage(err) - } - if v, ok := val.(lua.LBool); !ok { - return false - } else { - return bool(v) - } + + var action BufAction + switch k.(type) { + case KeyEvent, KeySequenceEvent, RawEvent: + action = BufKeyAction(func(h *BufPane) bool { + val, err := pl.Call(plFn, luar.New(ulua.L, h)) + if err != nil { + screen.TermMessage(err) + } + if v, ok := val.(lua.LBool); !ok { + return false + } else { + return bool(v) + } + }) + case MouseEvent: + action = BufMouseAction(func(h *BufPane, te *tcell.EventMouse) bool { + val, err := pl.Call(plFn, luar.New(ulua.L, h), luar.New(ulua.L, te)) + if err != nil { + screen.TermMessage(err) + } + if v, ok := val.(lua.LBool); !ok { + return false + } else { + return bool(v) + } + }) } + return action } -// BufMapKey maps an event to an action +// BufMapEvent maps an event to an action func BufMapEvent(k Event, action string) { config.Bindings["buffer"][k.Name()] = action - switch e := k.(type) { - case KeyEvent, KeySequenceEvent, RawEvent: - bufMapKey(e, action) - case MouseEvent: - bufMapMouse(e, action) - } -} - -func bufMapKey(k Event, action string) { - var actionfns []func(*BufPane) bool + var actionfns []BufAction var names []string var types []byte for i := 0; ; i++ { @@ -102,7 +114,7 @@ func bufMapKey(k Event, action string) { action = "" } - var afn func(*BufPane) bool + var afn BufAction if strings.HasPrefix(a, "command:") { a = strings.SplitN(a, ":", 2)[1] afn = CommandAction(a) @@ -113,7 +125,7 @@ func bufMapKey(k Event, action string) { names = append(names, "") } else if strings.HasPrefix(a, "lua:") { a = strings.SplitN(a, ":", 2)[1] - afn = LuaAction(a) + afn = LuaAction(a, k) if afn == nil { screen.TermMessage("Lua Error:", a, "does not exist") continue @@ -129,13 +141,16 @@ func bufMapKey(k Event, action string) { } else if f, ok := BufKeyActions[a]; ok { afn = f names = append(names, a) + } else if f, ok := BufMouseActions[a]; ok { + afn = f + names = append(names, a) } else { screen.TermMessage("Error in bindings: action", a, "does not exist") continue } actionfns = append(actionfns, afn) } - bufAction := func(h *BufPane) bool { + bufAction := func(h *BufPane, te *tcell.EventMouse) bool { cursors := h.Buf.GetCursors() success := true for i, a := range actionfns { @@ -147,7 +162,7 @@ func bufMapKey(k Event, action string) { h.Buf.SetCurCursor(c.Num) h.Cursor = c if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') { - innerSuccess = innerSuccess && h.execAction(a, names[i], j) + innerSuccess = innerSuccess && h.execAction(a, names[i], j, te) } else { break } @@ -159,17 +174,13 @@ func bufMapKey(k Event, action string) { return true } - BufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(bufAction)) -} - -// BufMapMouse maps a mouse event to an action -func bufMapMouse(k MouseEvent, action string) { - if f, ok := BufMouseActions[action]; ok { - BufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f)) - } else { - // TODO - // delete(BufMouseBindings, k) - bufMapKey(k, action) + switch e := k.(type) { + case KeyEvent, KeySequenceEvent, RawEvent: + BufBindings.RegisterKeyBinding(e, BufKeyActionGeneral(func(h *BufPane) bool { + return bufAction(h, nil) + })) + case MouseEvent: + BufBindings.RegisterMouseBinding(e, BufMouseActionGeneral(bufAction)) } } @@ -542,7 +553,7 @@ func (h *BufPane) DoKeyEvent(e Event) bool { return more } -func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool { +func (h *BufPane) execAction(action BufAction, name string, cursor int, te *tcell.EventMouse) bool { if name != "Autocomplete" && name != "CycleAutocompleteBack" { h.Buf.HasSuggestions = false } @@ -550,7 +561,13 @@ func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int _, isMulti := MultiActions[name] if (!isMulti && cursor == 0) || isMulti { if h.PluginCB("pre" + name) { - success := action(h) + var success bool + switch a := action.(type) { + case BufKeyAction: + success = a(h) + case BufMouseAction: + success = a(h, te) + } success = success && h.PluginCB("on"+name) if isMulti { From 3dba23a3482030c13b2158f81952dc842b705807 Mon Sep 17 00:00:00 2001 From: Dmytro Maluka Date: Thu, 14 Mar 2024 03:59:36 +0100 Subject: [PATCH 43/43] Minor: fix weird error message text when unable to load help (#2618) If we add something like this to init.lua: config.AddRuntimeFile("status", config.RTHelp, "help/foo.md") then start micro and run "help foo", the resulting error message looks weird: Unable to load help textfooopen plugins/status/help/foo.md: file does not exist Change it to: Unable to load help text for foo: open plugins/status/help/foo.md: file does not exist --- internal/action/command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/action/command.go b/internal/action/command.go index cc20d649..23d5bb93 100644 --- a/internal/action/command.go +++ b/internal/action/command.go @@ -375,7 +375,7 @@ func (h *BufPane) ReopenCmd(args []string) { func (h *BufPane) openHelp(page string) error { if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil { - return errors.New(fmt.Sprint("Unable to load help text", page, "\n", err)) + return errors.New(fmt.Sprintf("Unable to load help text for %s: %v", page, err)) } else { helpBuffer := buffer.NewBufferFromString(string(data), page+".md", buffer.BTHelp) helpBuffer.SetName("Help " + page)