From 5dbdf8c0e835be2a53610da20af12ba6368fbabc Mon Sep 17 00:00:00 2001 From: Massimo Mund Date: Tue, 14 May 2024 08:59:08 +0200 Subject: [PATCH] Implemented SubWordRight, SubWordLeft, SelectSubWordRight, SelectSubWordLeft and DeleteSubWordRight, DeleteSubWordLeft --- internal/action/actions.go | 60 ++++++++++++++++++ internal/action/bufpane.go | 12 ++++ internal/buffer/cursor.go | 121 ++++++++++++++++++++++++++++++++++++ internal/util/util.go | 57 ++++++++++++++++- runtime/help/keybindings.md | 6 ++ 5 files changed, 253 insertions(+), 3 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index 621cb55b..4f1b7cd6 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -283,6 +283,22 @@ func (h *BufPane) WordLeft() bool { return true } +// SubWordRight moves the cursor one sub-word to the right +func (h *BufPane) SubWordRight() bool { + h.Cursor.Deselect(false) + h.Cursor.SubWordRight() + h.Relocate() + return true +} + +// SubWordLeft moves the cursor one sub-word to the left +func (h *BufPane) SubWordLeft() bool { + h.Cursor.Deselect(true) + h.Cursor.SubWordLeft() + h.Relocate() + return true +} + // SelectUp selects up one line func (h *BufPane) SelectUp() bool { if !h.Cursor.HasSelection() { @@ -359,6 +375,28 @@ func (h *BufPane) SelectWordLeft() bool { return true } +// SelectSubWordRight selects the sub-word to the right of the cursor +func (h *BufPane) SelectSubWordRight() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.Cursor.SubWordRight() + h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() + return true +} + +// SelectSubWordLeft selects the sub-word to the left of the cursor +func (h *BufPane) SelectSubWordLeft() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.Cursor.SubWordLeft() + h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() + return true +} + // StartOfText moves the cursor to the start of the text of the line func (h *BufPane) StartOfText() bool { h.Cursor.Deselect(true) @@ -622,6 +660,28 @@ func (h *BufPane) DeleteWordLeft() bool { return true } +// DeleteSubWordRight deletes the sub-word to the right of the cursor +func (h *BufPane) DeleteSubWordRight() bool { + h.SelectSubWordRight() + if h.Cursor.HasSelection() { + h.Cursor.DeleteSelection() + h.Cursor.ResetSelection() + } + h.Relocate() + return true +} + +// DeleteSubWordLeft deletes the sub-word to the left of the cursor +func (h *BufPane) DeleteSubWordLeft() bool { + h.SelectSubWordLeft() + if h.Cursor.HasSelection() { + h.Cursor.DeleteSelection() + h.Cursor.ResetSelection() + } + h.Relocate() + return true +} + // Delete deletes the next character func (h *BufPane) Delete() bool { if h.Cursor.HasSelection() { diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index ff83360c..7b348b79 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -746,10 +746,16 @@ var BufKeyActions = map[string]BufKeyAction{ "SelectRight": (*BufPane).SelectRight, "WordRight": (*BufPane).WordRight, "WordLeft": (*BufPane).WordLeft, + "SubWordRight": (*BufPane).SubWordRight, + "SubWordLeft": (*BufPane).SubWordLeft, "SelectWordRight": (*BufPane).SelectWordRight, "SelectWordLeft": (*BufPane).SelectWordLeft, + "SelectSubWordRight": (*BufPane).SelectSubWordRight, + "SelectSubWordLeft": (*BufPane).SelectSubWordLeft, "DeleteWordRight": (*BufPane).DeleteWordRight, "DeleteWordLeft": (*BufPane).DeleteWordLeft, + "DeleteSubWordRight": (*BufPane).DeleteSubWordRight, + "DeleteSubWordLeft": (*BufPane).DeleteSubWordLeft, "SelectLine": (*BufPane).SelectLine, "SelectToStartOfLine": (*BufPane).SelectToStartOfLine, "SelectToStartOfText": (*BufPane).SelectToStartOfText, @@ -876,10 +882,16 @@ var MultiActions = map[string]bool{ "SelectRight": true, "WordRight": true, "WordLeft": true, + "SubWordRight": true, + "SubWordLeft": true, "SelectWordRight": true, "SelectWordLeft": true, + "SelectSubWordRight": true, + "SelectSubWordLeft": true, "DeleteWordRight": true, "DeleteWordLeft": true, + "DeleteSubWordRight": true, + "DeleteSubWordLeft": true, "SelectLine": true, "SelectToStartOfLine": true, "SelectToStartOfText": true, diff --git a/internal/buffer/cursor.go b/internal/buffer/cursor.go index bd3ae068..29ffa846 100644 --- a/internal/buffer/cursor.go +++ b/internal/buffer/cursor.go @@ -438,6 +438,127 @@ func (c *Cursor) WordLeft() { c.Right() } +// SubWordRight moves the cursor one sub-word to the right +func (c *Cursor) SubWordRight() { + if util.IsWhitespace(c.RuneUnder(c.X)) { + for util.IsWhitespace(c.RuneUnder(c.X)) { + if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { + c.Right() + return + } + c.Right() + } + return + } + if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) { + for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) { + if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { + c.Right() + return + } + c.Right() + } + return + } + if util.IsSubwordDelimiter(c.RuneUnder(c.X)) { + for util.IsSubwordDelimiter(c.RuneUnder(c.X)) { + if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { + c.Right() + return + } + c.Right() + } + if util.IsWhitespace(c.RuneUnder(c.X)) { + return + } + } + if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { + return + } + if util.IsUpperLetter(c.RuneUnder(c.X)) && + util.IsUpperLetter(c.RuneUnder(c.X+1)) { + for util.IsUpperAlphanumeric(c.RuneUnder(c.X)) { + if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { + return + } + c.Right() + } + if util.IsLowerAlphanumeric(c.RuneUnder(c.X)) { + c.Left() + } + } else { + c.Right() + for util.IsLowerAlphanumeric(c.RuneUnder(c.X)) { + if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { + return + } + c.Right() + } + } +} + +// SubWordLeft moves the cursor one sub-word to the left +func (c *Cursor) SubWordLeft() { + c.Left() + if util.IsWhitespace(c.RuneUnder(c.X)) { + for util.IsWhitespace(c.RuneUnder(c.X)) { + if c.X == 0 { + return + } + c.Left() + } + c.Right() + return + } + if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) { + for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) { + if c.X == 0 { + return + } + c.Left() + } + c.Right() + return + } + if util.IsSubwordDelimiter(c.RuneUnder(c.X)) { + for util.IsSubwordDelimiter(c.RuneUnder(c.X)) { + if c.X == 0 { + return + } + c.Left() + } + if util.IsWhitespace(c.RuneUnder(c.X)) { + c.Right() + return + } + } + if c.X == 0 { + return + } + if util.IsUpperLetter(c.RuneUnder(c.X)) && + util.IsUpperLetter(c.RuneUnder(c.X-1)) { + for util.IsUpperAlphanumeric(c.RuneUnder(c.X)) { + if c.X == 0 { + return + } + c.Left() + } + if !util.IsUpperAlphanumeric(c.RuneUnder(c.X)) { + c.Right() + } + } else { + for util.IsLowerAlphanumeric(c.RuneUnder(c.X)) { + if c.X == 0 { + return + } + c.Left() + } + if !util.IsAlphanumeric(c.RuneUnder(c.X)) { + c.Right() + } + } +} + // RuneUnder returns the rune under the given x position func (c *Cursor) RuneUnder(x int) rune { line := c.buf.LineBytes(c.Y) diff --git a/internal/util/util.go b/internal/util/util.go index b7bebcaf..1cd5d46c 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -219,18 +219,69 @@ func FSize(f *os.File) int64 { } // IsWordChar returns whether or not a rune is a 'word character' -// Word characters are defined as numbers, letters or '_' +// Word characters are defined as numbers, letters or sub-word delimiters func IsWordChar(r rune) bool { - return unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' + return IsAlphanumeric(r) || IsSubwordDelimiter(r) } // IsNonWordChar returns whether or not a rune is not a 'word character' -// Non word characters are defined as all characters not being numbers, letters or '_' +// Non word characters are defined as all characters not being numbers, letters or sub-word delimiters // See IsWordChar() func IsNonWordChar(r rune) bool { return !IsWordChar(r) } +// IsUpperWordChar returns whether or not a rune is an 'upper word character' +// Upper word characters are defined as numbers, upper-case letters or sub-word delimiters +func IsUpperWordChar(r rune) bool { + return IsUpperAlphanumeric(r) || IsSubwordDelimiter(r) +} + +// IsLowerWordChar returns whether or not a rune is a 'lower word character' +// Lower word characters are defined as numbers, lower-case letters or sub-word delimiters +func IsLowerWordChar(r rune) bool { + return IsLowerAlphanumeric(r) || IsSubwordDelimiter(r) +} + +// IsSubwordDelimiter returns whether or not a rune is a 'sub-word delimiter character' +// i.e. is considered a part of the word and is used as a delimiter between sub-words of the word. +// For now the only sub-word delimiter character is '_'. +func IsSubwordDelimiter(r rune) bool { + return r == '_' +} + +// IsAlphanumeric returns whether or not a rune is an 'alphanumeric character' +// Alphanumeric characters are defined as numbers or letters +func IsAlphanumeric(r rune) bool { + return unicode.IsLetter(r) || unicode.IsNumber(r) +} + +// IsUpperAlphanumeric returns whether or not a rune is an 'upper alphanumeric character' +// Upper alphanumeric characters are defined as numbers or upper-case letters +func IsUpperAlphanumeric(r rune) bool { + return IsUpperLetter(r) || unicode.IsNumber(r) +} + +// IsLowerAlphanumeric returns whether or not a rune is a 'lower alphanumeric character' +// Lower alphanumeric characters are defined as numbers or lower-case letters +func IsLowerAlphanumeric(r rune) bool { + return IsLowerLetter(r) || unicode.IsNumber(r) +} + +// IsUpperLetter returns whether or not a rune is an 'upper letter character' +// Upper letter characters are defined as upper-case letters +func IsUpperLetter(r rune) bool { + // unicode.IsUpper() returns true for letters only + return unicode.IsUpper(r) +} + +// IsLowerLetter returns whether or not a rune is a 'lower letter character' +// Lower letter characters are defined as lower-case letters +func IsLowerLetter(r rune) bool { + // unicode.IsLower() returns true for letters only + return unicode.IsLower(r) +} + // Spaces returns a string with n spaces func Spaces(n int) string { return strings.Repeat(" ", n) diff --git a/runtime/help/keybindings.md b/runtime/help/keybindings.md index e0c2dd9b..17f9ab35 100644 --- a/runtime/help/keybindings.md +++ b/runtime/help/keybindings.md @@ -178,12 +178,18 @@ SelectToStartOfText SelectToStartOfTextToggle WordRight WordLeft +SubWordRight +SubWordLeft SelectWordRight SelectWordLeft +SelectSubWordRight +SelectSubWordLeft MoveLinesUp MoveLinesDown DeleteWordRight DeleteWordLeft +DeleteSubWordRight +DeleteSubWordLeft SelectLine SelectToStartOfLine SelectToEndOfLine