207 lines
6.0 KiB
Go
207 lines
6.0 KiB
Go
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
|
|
|
package readline
|
|
|
|
import (
|
|
"fmt"
|
|
"kitty/tools/tui/loop"
|
|
"kitty/tools/utils"
|
|
"kitty/tools/wcswidth"
|
|
"strings"
|
|
)
|
|
|
|
var _ = fmt.Print
|
|
|
|
func (self *Readline) update_current_screen_size() {
|
|
var screen_size loop.ScreenSize
|
|
var err error
|
|
if self.loop != nil {
|
|
screen_size, err = self.loop.ScreenSize()
|
|
if err != nil {
|
|
screen_size.WidthCells = 80
|
|
screen_size.HeightCells = 24
|
|
}
|
|
} else {
|
|
screen_size.WidthCells = 80
|
|
screen_size.HeightCells = 24
|
|
}
|
|
self.screen_width = utils.Max(1, int(screen_size.WidthCells))
|
|
self.screen_height = utils.Max(1, int(screen_size.HeightCells))
|
|
}
|
|
|
|
type ScreenLine struct {
|
|
ParentLineNumber, OffsetInParentLine int
|
|
Prompt Prompt
|
|
TextLengthInCells, CursorCell, CursorTextPos int
|
|
Text string
|
|
}
|
|
|
|
func (self *Readline) format_arg_prompt(cna string) string {
|
|
return fmt.Sprintf("(arg: %s) ", self.fmt_ctx.Yellow(cna))
|
|
}
|
|
|
|
func (self *Readline) prompt_for_line_number(i int) Prompt {
|
|
is_line_with_cursor := i == self.input_state.cursor.Y
|
|
if is_line_with_cursor && self.keyboard_state.current_numeric_argument != "" {
|
|
return self.make_prompt(self.format_arg_prompt(self.keyboard_state.current_numeric_argument), i > 0)
|
|
}
|
|
if i == 0 {
|
|
if self.history_search != nil {
|
|
return self.make_prompt(self.history_search_prompt(), i > 0)
|
|
}
|
|
return self.prompt
|
|
}
|
|
return self.continuation_prompt
|
|
}
|
|
|
|
func (self *Readline) apply_syntax_highlighting() (lines []string, cursor Position) {
|
|
highlighter := self.syntax_highlighted.highlighter
|
|
highlighter_name := "default"
|
|
if self.history_search != nil {
|
|
highlighter = self.history_search_highlighter
|
|
highlighter_name = "## history ##"
|
|
}
|
|
if highlighter == nil {
|
|
return self.input_state.lines, self.input_state.cursor
|
|
}
|
|
src := strings.Join(self.input_state.lines, "\n")
|
|
if len(self.syntax_highlighted.lines) > 0 && self.syntax_highlighted.last_highlighter_name == highlighter_name && self.syntax_highlighted.src_for_last_highlight == src {
|
|
lines = self.syntax_highlighted.lines
|
|
} else {
|
|
if src == "" {
|
|
lines = []string{""}
|
|
} else {
|
|
text := highlighter(src, self.input_state.cursor.X, self.input_state.cursor.Y)
|
|
lines = utils.Splitlines(text)
|
|
for len(lines) < len(self.input_state.lines) {
|
|
lines = append(lines, "syntax highlighter malfunctioned")
|
|
}
|
|
}
|
|
}
|
|
line := lines[self.input_state.cursor.Y]
|
|
w := wcswidth.Stringwidth(self.input_state.lines[self.input_state.cursor.Y][:self.input_state.cursor.X])
|
|
x := len(wcswidth.TruncateToVisualLength(line, w))
|
|
return lines, Position{X: x, Y: self.input_state.cursor.Y}
|
|
}
|
|
|
|
func (self *Readline) get_screen_lines() []*ScreenLine {
|
|
if self.screen_width == 0 || self.screen_height == 0 {
|
|
self.update_current_screen_size()
|
|
}
|
|
lines, cursor := self.apply_syntax_highlighting()
|
|
ans := make([]*ScreenLine, 0, len(lines))
|
|
found_cursor := false
|
|
cursor_at_start_of_next_line := false
|
|
for i, line := range lines {
|
|
prompt := self.prompt_for_line_number(i)
|
|
offset := 0
|
|
has_cursor := i == cursor.Y
|
|
for is_first := true; is_first || offset < len(line); is_first = false {
|
|
l, width := wcswidth.TruncateToVisualLengthWithWidth(line[offset:], self.screen_width-prompt.Length)
|
|
sl := ScreenLine{
|
|
ParentLineNumber: i, OffsetInParentLine: offset,
|
|
Prompt: prompt, TextLengthInCells: width,
|
|
CursorCell: -1, Text: l, CursorTextPos: -1,
|
|
}
|
|
if cursor_at_start_of_next_line {
|
|
cursor_at_start_of_next_line = false
|
|
sl.CursorCell = prompt.Length
|
|
sl.CursorTextPos = 0
|
|
found_cursor = true
|
|
}
|
|
ans = append(ans, &sl)
|
|
if has_cursor && !found_cursor && offset <= cursor.X && cursor.X <= offset+len(l) {
|
|
found_cursor = true
|
|
ctpos := cursor.X - offset
|
|
ccell := prompt.Length + wcswidth.Stringwidth(l[:ctpos])
|
|
if ccell >= self.screen_width {
|
|
if offset+len(l) < len(line) || i < len(lines)-1 {
|
|
cursor_at_start_of_next_line = true
|
|
} else {
|
|
ans = append(ans, &ScreenLine{ParentLineNumber: i, OffsetInParentLine: len(line)})
|
|
}
|
|
} else {
|
|
sl.CursorTextPos = ctpos
|
|
sl.CursorCell = ccell
|
|
}
|
|
}
|
|
prompt = Prompt{}
|
|
offset += len(l)
|
|
}
|
|
}
|
|
return ans
|
|
}
|
|
|
|
func (self *Readline) redraw() {
|
|
if self.screen_width == 0 || self.screen_height == 0 {
|
|
self.update_current_screen_size()
|
|
}
|
|
if self.screen_width < 4 {
|
|
return
|
|
}
|
|
if self.cursor_y > 0 {
|
|
self.loop.MoveCursorVertically(-self.cursor_y)
|
|
}
|
|
self.loop.QueueWriteString("\r")
|
|
self.loop.ClearToEndOfScreen()
|
|
prompt_lines := self.get_screen_lines()
|
|
csl, csl_cached := self.completion_screen_lines()
|
|
render_completion_above := len(csl)+len(prompt_lines) > self.screen_height
|
|
completion_needs_render := len(csl) > 0 && (!render_completion_above || !self.completions.current.last_rendered_above || !csl_cached)
|
|
cursor_x := -1
|
|
cursor_y := 0
|
|
move_cursor_up_by := 0
|
|
|
|
render_completion_lines := func() int {
|
|
if completion_needs_render {
|
|
if render_completion_above {
|
|
self.loop.QueueWriteString("\r")
|
|
} else {
|
|
self.loop.QueueWriteString("\r\n")
|
|
}
|
|
for i, cl := range csl {
|
|
self.loop.QueueWriteString(cl)
|
|
if i < len(csl)-1 || render_completion_above {
|
|
self.loop.QueueWriteString("\n\r")
|
|
}
|
|
|
|
}
|
|
return len(csl)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
self.loop.AllowLineWrapping(false)
|
|
if render_completion_above {
|
|
render_completion_lines()
|
|
}
|
|
for i, sl := range prompt_lines {
|
|
self.loop.QueueWriteString("\r")
|
|
if i > 0 {
|
|
self.loop.QueueWriteString("\n")
|
|
}
|
|
if sl.Prompt.Length > 0 {
|
|
self.loop.QueueWriteString(self.prompt_for_line_number(i).Text)
|
|
}
|
|
self.loop.QueueWriteString(sl.Text)
|
|
if sl.CursorCell > -1 {
|
|
cursor_x = sl.CursorCell
|
|
} else if cursor_x > -1 {
|
|
move_cursor_up_by++
|
|
}
|
|
cursor_y++
|
|
}
|
|
if !render_completion_above {
|
|
move_cursor_up_by += render_completion_lines()
|
|
}
|
|
self.loop.AllowLineWrapping(true)
|
|
self.loop.MoveCursorVertically(-move_cursor_up_by)
|
|
self.loop.QueueWriteString("\r")
|
|
self.loop.MoveCursorHorizontally(cursor_x)
|
|
self.cursor_y = 0
|
|
cursor_y -= move_cursor_up_by
|
|
if cursor_y > 0 {
|
|
self.cursor_y = cursor_y - 1
|
|
}
|
|
}
|