Fix high prio recommendations, add error / health screen
This commit is contained in:
@@ -3,10 +3,12 @@ package main
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
type textEditorMode string
|
||||
@@ -69,7 +71,7 @@ func (e *textEditor) handleKeyAtWidth(key tea.KeyMsg, multiline bool, wrapWidth
|
||||
e.clearPending()
|
||||
start, _ := editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
if e.Cursor > start {
|
||||
e.Cursor--
|
||||
e.Cursor = max(start, previousGraphemeBoundary(e.Text, e.Cursor))
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -92,10 +94,10 @@ func (e *textEditor) handleStandardKey(key tea.KeyMsg, multiline bool, wrapWidth
|
||||
switch {
|
||||
case key.Type != tea.KeyRunes && key.Type != tea.KeySpace &&
|
||||
keyMatches(k, e.keys.Navigation.Left):
|
||||
e.Cursor = max(0, e.Cursor-1)
|
||||
e.Cursor = previousGraphemeBoundary(e.Text, e.Cursor)
|
||||
case key.Type != tea.KeyRunes && key.Type != tea.KeySpace &&
|
||||
keyMatches(k, e.keys.Navigation.Right):
|
||||
e.Cursor = min(len([]rune(e.Text)), e.Cursor+1)
|
||||
e.Cursor = nextGraphemeBoundary(e.Text, e.Cursor)
|
||||
case key.Type != tea.KeyRunes && key.Type != tea.KeySpace &&
|
||||
keyMatches(k, e.keys.Navigation.Up):
|
||||
if !multiline {
|
||||
@@ -130,6 +132,7 @@ func (e *textEditor) handleStandardKey(key tea.KeyMsg, multiline bool, wrapWidth
|
||||
return false
|
||||
}
|
||||
}
|
||||
e.Cursor = previousOrCurrentGraphemeBoundary(e.Text, e.Cursor)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -166,7 +169,7 @@ func (e *textEditor) handleNormalKey(key tea.KeyMsg, multiline bool, wrapWidth i
|
||||
e.Mode = textEditorInsert
|
||||
case keyMatches(k, e.keys.Vim.Append):
|
||||
_, end := editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
e.Cursor = min(end, e.Cursor+1)
|
||||
e.Cursor = min(end, nextGraphemeBoundary(e.Text, e.Cursor))
|
||||
e.Mode = textEditorInsert
|
||||
case keyMatches(k, e.keys.Vim.InsertLineStart):
|
||||
e.Cursor = firstNonBlankAtWidth(e.Text, e.Cursor, wrapWidth)
|
||||
@@ -192,9 +195,12 @@ func (e *textEditor) handleNormalKey(key tea.KeyMsg, multiline bool, wrapWidth i
|
||||
e.Mode = textEditorInsert
|
||||
case keyMatches(k, e.keys.Navigation.Left):
|
||||
start, _ := editorLineBounds(e.Text, e.Cursor, wrapWidth)
|
||||
e.Cursor = max(start, e.Cursor-1)
|
||||
e.Cursor = max(start, previousGraphemeBoundary(e.Text, e.Cursor))
|
||||
case keyMatches(k, e.keys.Navigation.Right):
|
||||
e.Cursor = min(normalEditorLineLast(e.Text, e.Cursor, wrapWidth), e.Cursor+1)
|
||||
e.Cursor = min(
|
||||
normalEditorLineLast(e.Text, e.Cursor, wrapWidth),
|
||||
nextGraphemeBoundary(e.Text, e.Cursor),
|
||||
)
|
||||
case keyMatches(k, e.keys.Navigation.Down):
|
||||
if multiline {
|
||||
e.Cursor = moveEditorCursorLine(e.Text, e.Cursor, 1, wrapWidth, true)
|
||||
@@ -266,6 +272,7 @@ func (e *textEditor) handleNormalKey(key tea.KeyMsg, multiline bool, wrapWidth i
|
||||
default:
|
||||
return false
|
||||
}
|
||||
e.Cursor = previousOrCurrentGraphemeBoundary(e.Text, e.Cursor)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -347,7 +354,9 @@ func (e textEditor) selectionBounds(wrapWidth int) (int, int, bool) {
|
||||
anchor := clamp(e.visualAnchor, 0, len(runes))
|
||||
cursor := clamp(e.Cursor, 0, len(runes))
|
||||
if !e.visualLine {
|
||||
start, end := min(anchor, cursor), min(len(runes), max(anchor, cursor)+1)
|
||||
start := previousOrCurrentGraphemeBoundary(e.Text, min(anchor, cursor))
|
||||
end := nextGraphemeBoundary(e.Text, max(anchor, cursor))
|
||||
end = min(len(runes), end)
|
||||
return start, end, end > start
|
||||
}
|
||||
anchorStart, anchorEnd := editorLineBounds(e.Text, anchor, wrapWidth)
|
||||
@@ -386,7 +395,7 @@ func (e *textEditor) deleteSelection(wrapWidth int) {
|
||||
e.Text = string(append(runes[:start], runes[end:]...))
|
||||
e.Cursor = min(start, len([]rune(e.Text)))
|
||||
if e.Cursor == len([]rune(e.Text)) && e.Cursor > 0 {
|
||||
e.Cursor--
|
||||
e.Cursor = previousGraphemeBoundary(e.Text, e.Cursor)
|
||||
}
|
||||
e.stopVisual()
|
||||
}
|
||||
@@ -411,11 +420,11 @@ func (e *textEditor) pasteClipboard(replaceSelection bool, wrapWidth int) {
|
||||
e.stopVisual()
|
||||
} else {
|
||||
_, end := editorLineBounds(e.Text, e.Cursor, 0)
|
||||
e.Cursor = min(end, e.Cursor+1)
|
||||
e.Cursor = min(end, nextGraphemeBoundary(e.Text, e.Cursor))
|
||||
}
|
||||
e.insert(value)
|
||||
if e.Cursor > 0 {
|
||||
e.Cursor--
|
||||
e.Cursor = previousGraphemeBoundary(e.Text, e.Cursor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,8 +451,9 @@ func (e *textEditor) deleteBefore() {
|
||||
if e.Cursor == 0 {
|
||||
return
|
||||
}
|
||||
value = append(value[:e.Cursor-1], value[e.Cursor:]...)
|
||||
e.Cursor--
|
||||
start := previousGraphemeBoundary(e.Text, e.Cursor)
|
||||
value = append(value[:start], value[e.Cursor:]...)
|
||||
e.Cursor = start
|
||||
e.Text = string(value)
|
||||
}
|
||||
|
||||
@@ -453,10 +463,56 @@ func (e *textEditor) deleteAt() {
|
||||
if e.Cursor == len(value) {
|
||||
return
|
||||
}
|
||||
value = append(value[:e.Cursor], value[e.Cursor+1:]...)
|
||||
end := nextGraphemeBoundary(e.Text, e.Cursor)
|
||||
value = append(value[:e.Cursor], value[end:]...)
|
||||
e.Text = string(value)
|
||||
}
|
||||
|
||||
func graphemeBoundaries(value string) []int {
|
||||
boundaries := []int{0}
|
||||
graphemes := uniseg.NewGraphemes(value)
|
||||
offset := 0
|
||||
for graphemes.Next() {
|
||||
offset += utf8.RuneCountInString(graphemes.Str())
|
||||
boundaries = append(boundaries, offset)
|
||||
}
|
||||
return boundaries
|
||||
}
|
||||
|
||||
func previousGraphemeBoundary(value string, cursor int) int {
|
||||
cursor = clamp(cursor, 0, len([]rune(value)))
|
||||
previous := 0
|
||||
for _, boundary := range graphemeBoundaries(value) {
|
||||
if boundary >= cursor {
|
||||
return previous
|
||||
}
|
||||
previous = boundary
|
||||
}
|
||||
return previous
|
||||
}
|
||||
|
||||
func previousOrCurrentGraphemeBoundary(value string, cursor int) int {
|
||||
cursor = clamp(cursor, 0, len([]rune(value)))
|
||||
previous := 0
|
||||
for _, boundary := range graphemeBoundaries(value) {
|
||||
if boundary > cursor {
|
||||
return previous
|
||||
}
|
||||
previous = boundary
|
||||
}
|
||||
return previous
|
||||
}
|
||||
|
||||
func nextGraphemeBoundary(value string, cursor int) int {
|
||||
cursor = clamp(cursor, 0, len([]rune(value)))
|
||||
for _, boundary := range graphemeBoundaries(value) {
|
||||
if boundary > cursor {
|
||||
return boundary
|
||||
}
|
||||
}
|
||||
return len([]rune(value))
|
||||
}
|
||||
|
||||
func (e *textEditor) performFind(command, target rune, remember bool, wrapWidth int) {
|
||||
runes := []rune(e.Text)
|
||||
cursor := clamp(e.Cursor, 0, len(runes))
|
||||
|
||||
Reference in New Issue
Block a user