|
@@ -956,22 +956,77 @@ func (m *Model) CursorUp() {
|
|
|
m.lastCharOffset = charOffset
|
|
m.lastCharOffset = charOffset
|
|
|
|
|
|
|
|
if li.RowOffset <= 0 && m.row > 0 {
|
|
if li.RowOffset <= 0 && m.row > 0 {
|
|
|
- // Move to the previous model line
|
|
|
|
|
|
|
+ // Move to the previous model line. We want to land on the last wrapped
|
|
|
|
|
+ // line of the previous model line.
|
|
|
m.row--
|
|
m.row--
|
|
|
- m.col = m.mapVisualOffsetToSliceIndex(m.row, charOffset)
|
|
|
|
|
|
|
+ grid := m.memoizedWrap(m.value[m.row], m.width)
|
|
|
|
|
+ targetLineContent := grid[len(grid)-1]
|
|
|
|
|
+
|
|
|
|
|
+ // Find start of last wrapped line.
|
|
|
|
|
+ startCol := len(m.value[m.row]) - len(targetLineContent)
|
|
|
|
|
+
|
|
|
|
|
+ // Find position within the last wrapped line.
|
|
|
|
|
+ offset := 0
|
|
|
|
|
+ colInLine := 0
|
|
|
|
|
+ for i, item := range targetLineContent {
|
|
|
|
|
+ var itemWidth int
|
|
|
|
|
+ switch v := item.(type) {
|
|
|
|
|
+ case rune:
|
|
|
|
|
+ itemWidth = rw.RuneWidth(v)
|
|
|
|
|
+ case *Attachment:
|
|
|
|
|
+ itemWidth = uniseg.StringWidth(v.Display)
|
|
|
|
|
+ }
|
|
|
|
|
+ if offset+itemWidth > charOffset {
|
|
|
|
|
+ // Decide whether to stick with the previous index or move to the current
|
|
|
|
|
+ // one based on which is closer to the target offset.
|
|
|
|
|
+ if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
|
|
|
|
|
+ colInLine = i + 1
|
|
|
|
|
+ } else {
|
|
|
|
|
+ colInLine = i
|
|
|
|
|
+ }
|
|
|
|
|
+ goto foundPrevLine
|
|
|
|
|
+ }
|
|
|
|
|
+ offset += itemWidth
|
|
|
|
|
+ }
|
|
|
|
|
+ colInLine = len(targetLineContent)
|
|
|
|
|
+ foundPrevLine:
|
|
|
|
|
+ m.col = startCol + colInLine
|
|
|
} else if li.RowOffset > 0 {
|
|
} else if li.RowOffset > 0 {
|
|
|
- // Move to the previous wrapped line within the same model line
|
|
|
|
|
- // To do this, we need to find the start of the previous wrapped line.
|
|
|
|
|
- prevLineInfo := m.LineInfo()
|
|
|
|
|
- // prevLineStart := 0
|
|
|
|
|
- if prevLineInfo.RowOffset > 0 {
|
|
|
|
|
- // This is complex, so we'll approximate by moving to the start of the current wrapped line
|
|
|
|
|
- // and then letting characterLeft handle it. A more precise calculation would
|
|
|
|
|
- // require re-wrapping to find the previous line's start.
|
|
|
|
|
- // For now, a simpler approach:
|
|
|
|
|
- m.col = li.StartColumn - 1
|
|
|
|
|
|
|
+ // Move to the previous wrapped line within the same model line.
|
|
|
|
|
+ grid := m.memoizedWrap(m.value[m.row], m.width)
|
|
|
|
|
+ targetLineContent := grid[li.RowOffset-1]
|
|
|
|
|
+
|
|
|
|
|
+ startCol := 0
|
|
|
|
|
+ for i := 0; i < li.RowOffset-1; i++ {
|
|
|
|
|
+ startCol += len(grid[i])
|
|
|
}
|
|
}
|
|
|
- m.col = m.mapVisualOffsetToSliceIndex(m.row, charOffset)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Find position within the target wrapped line.
|
|
|
|
|
+ offset := 0
|
|
|
|
|
+ colInLine := 0
|
|
|
|
|
+ for i, item := range targetLineContent {
|
|
|
|
|
+ var itemWidth int
|
|
|
|
|
+ switch v := item.(type) {
|
|
|
|
|
+ case rune:
|
|
|
|
|
+ itemWidth = rw.RuneWidth(v)
|
|
|
|
|
+ case *Attachment:
|
|
|
|
|
+ itemWidth = uniseg.StringWidth(v.Display)
|
|
|
|
|
+ }
|
|
|
|
|
+ if offset+itemWidth > charOffset {
|
|
|
|
|
+ // Decide whether to stick with the previous index or move to the current
|
|
|
|
|
+ // one based on which is closer to the target offset.
|
|
|
|
|
+ if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
|
|
|
|
|
+ colInLine = i + 1
|
|
|
|
|
+ } else {
|
|
|
|
|
+ colInLine = i
|
|
|
|
|
+ }
|
|
|
|
|
+ goto foundSameLine
|
|
|
|
|
+ }
|
|
|
|
|
+ offset += itemWidth
|
|
|
|
|
+ }
|
|
|
|
|
+ colInLine = len(targetLineContent)
|
|
|
|
|
+ foundSameLine:
|
|
|
|
|
+ m.col = startCol + colInLine
|
|
|
}
|
|
}
|
|
|
m.SetCursorColumn(m.col)
|
|
m.SetCursorColumn(m.col)
|
|
|
}
|
|
}
|
|
@@ -1591,7 +1646,7 @@ func (m Model) View() string {
|
|
|
// guaranteed to be a space since any other character would
|
|
// guaranteed to be a space since any other character would
|
|
|
// have been wrapped.
|
|
// have been wrapped.
|
|
|
wrappedLineStr = strings.TrimSuffix(wrappedLineStr, " ")
|
|
wrappedLineStr = strings.TrimSuffix(wrappedLineStr, " ")
|
|
|
- padding -= m.width - strwidth
|
|
|
|
|
|
|
+ padding = m.width - uniseg.StringWidth(wrappedLineStr)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if m.row == l && lineInfo.RowOffset == wl {
|
|
if m.row == l && lineInfo.RowOffset == wl {
|
|
@@ -1903,6 +1958,16 @@ func (m *Model) splitLine(row, col int) {
|
|
|
m.row++
|
|
m.row++
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func itemWidth(item any) int {
|
|
|
|
|
+ switch v := item.(type) {
|
|
|
|
|
+ case rune:
|
|
|
|
|
+ return rw.RuneWidth(v)
|
|
|
|
|
+ case *Attachment:
|
|
|
|
|
+ return uniseg.StringWidth(v.Display)
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func wrapInterfaces(content []any, width int) [][]any {
|
|
func wrapInterfaces(content []any, width int) [][]any {
|
|
|
if width <= 0 {
|
|
if width <= 0 {
|
|
|
return [][]any{content}
|
|
return [][]any{content}
|
|
@@ -1945,19 +2010,18 @@ func wrapInterfaces(content []any, width int) [][]any {
|
|
|
}
|
|
}
|
|
|
inSpaces = true
|
|
inSpaces = true
|
|
|
spaceW += itemW
|
|
spaceW += itemW
|
|
|
- } else {
|
|
|
|
|
|
|
+ } else { // It's not a space, it's a character for a word.
|
|
|
if inSpaces {
|
|
if inSpaces {
|
|
|
- // End of spaces
|
|
|
|
|
- if lineW > 0 && lineW+spaceW > width {
|
|
|
|
|
- lines = append(lines, []any{})
|
|
|
|
|
- lineW = 0
|
|
|
|
|
- } else {
|
|
|
|
|
- lineW += spaceW
|
|
|
|
|
- }
|
|
|
|
|
- // Add spaces to current line
|
|
|
|
|
|
|
+ // We just finished a block of spaces. Handle them now.
|
|
|
|
|
+ lineW += spaceW
|
|
|
for i := 0; i < spaceW; i++ {
|
|
for i := 0; i < spaceW; i++ {
|
|
|
lines[len(lines)-1] = append(lines[len(lines)-1], rune(' '))
|
|
lines[len(lines)-1] = append(lines[len(lines)-1], rune(' '))
|
|
|
}
|
|
}
|
|
|
|
|
+ if lineW > width {
|
|
|
|
|
+ // The spaces made the line overflow. Start a new line for the upcoming word.
|
|
|
|
|
+ lines = append(lines, []any{})
|
|
|
|
|
+ lineW = 0
|
|
|
|
|
+ }
|
|
|
spaceW = 0
|
|
spaceW = 0
|
|
|
}
|
|
}
|
|
|
inSpaces = false
|
|
inSpaces = false
|
|
@@ -1966,20 +2030,24 @@ func wrapInterfaces(content []any, width int) [][]any {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Handle any remaining word/spaces
|
|
|
|
|
|
|
+ // Handle any remaining word/spaces at the end of the content.
|
|
|
if wordW > 0 {
|
|
if wordW > 0 {
|
|
|
if lineW > 0 && lineW+wordW > width {
|
|
if lineW > 0 && lineW+wordW > width {
|
|
|
lines = append(lines, word)
|
|
lines = append(lines, word)
|
|
|
|
|
+ lineW = wordW
|
|
|
} else {
|
|
} else {
|
|
|
lines[len(lines)-1] = append(lines[len(lines)-1], word...)
|
|
lines[len(lines)-1] = append(lines[len(lines)-1], word...)
|
|
|
|
|
+ lineW += wordW
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
if spaceW > 0 {
|
|
if spaceW > 0 {
|
|
|
- if lineW > 0 && lineW+spaceW > width {
|
|
|
|
|
- lines = append(lines, []any{})
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // There are trailing spaces. Add them.
|
|
|
for i := 0; i < spaceW; i++ {
|
|
for i := 0; i < spaceW; i++ {
|
|
|
lines[len(lines)-1] = append(lines[len(lines)-1], rune(' '))
|
|
lines[len(lines)-1] = append(lines[len(lines)-1], rune(' '))
|
|
|
|
|
+ lineW += 1
|
|
|
|
|
+ }
|
|
|
|
|
+ if lineW > width {
|
|
|
|
|
+ lines = append(lines, []any{})
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|