about summary refs log tree commit diff stats
path: root/src/io
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-11-29 12:28:04 +0100
committerbptato <nincsnevem662@gmail.com>2022-11-29 12:28:04 +0100
commitd7a05e7a58d0c2de3078fc9854534974c7e347d4 (patch)
tree7e5bc4327631ac4106d885b5d5e74bfb1cfa3d64 /src/io
parentf65667797376e65d8e19e1aa514d160210013540 (diff)
downloadchawan-d7a05e7a58d0c2de3078fc9854534974c7e347d4.tar.gz
Add line editing history, other lineedit fixes
It's still kind of broken...
Diffstat (limited to 'src/io')
-rw-r--r--src/io/lineedit.nim195
1 files changed, 112 insertions, 83 deletions
diff --git a/src/io/lineedit.nim b/src/io/lineedit.nim
index 2cd76fff..d19fe71e 100644
--- a/src/io/lineedit.nim
+++ b/src/io/lineedit.nim
@@ -14,6 +14,9 @@ type
   LineEditState* = enum
     EDIT, FINISH, CANCEL
 
+  LineHistory* = ref object
+    lines: seq[string]
+
   LineEdit* = ref object
     news*: seq[Rune]
     prompt*: string
@@ -24,39 +27,17 @@ type
     cursor: int
     shift: int
     minlen: int
-    maxlen: int
+    maxwidth: int
     displen: int
     disallowed: set[char]
     hide: bool
     term: Terminal
+    hist: LineHistory
+    histindex: int
+    histtmp: string
 
-proc printesc(edit: LineEdit, rs: seq[Rune]) =
-  var s = ""
-  var format = newFormat()
-  var cformat = newFormat()
-  cformat.fgcolor = ColorsANSIFg[4] # blue
-  var dformat = newFormat() # reset
-  for r in rs:
-    if r.isControlChar():
-      s &= edit.term.processFormat(format, cformat)
-    else:
-      s &= edit.term.processFormat(format, dformat)
-    s &= r
-  edit.term.write(s)
-
-proc printesc(edit: LineEdit, s: string) =
-  var s = ""
-  var format = newFormat()
-  var cformat = newFormat()
-  cformat.fgcolor = ColorsANSIFg[4] # blue
-  var dformat = newFormat() # reset
-  for r in s.runes:
-    if r.isControlChar():
-      s &= edit.term.processFormat(format, cformat)
-    else:
-      s &= edit.term.processFormat(format, dformat)
-    s &= r
-  edit.term.write(s)
+func newLineHistory*(): LineHistory =
+  return LineHistory()
 
 func lwidth(r: Rune): int =
   if r.isControlChar():
@@ -84,6 +65,33 @@ func lwidth(s: seq[Rune], min: int): int =
     result += lwidth(s[i])
     inc i
 
+const colorFormat = (func(): Format =
+  result = newFormat()
+  result.fgcolor = ColorsANSIFg[4] # blue
+)()
+const defaultFormat = newFormat()
+proc printesc(edit: LineEdit, rs: seq[Rune]) =
+  var s = ""
+  var format = newFormat()
+  for r in rs:
+    if r.isControlChar():
+      s &= edit.term.processFormat(format, colorFormat)
+    else:
+      s &= edit.term.processFormat(format, defaultFormat)
+    s &= r
+  edit.term.write(s)
+
+proc printesc(edit: LineEdit, s: string) =
+  var s = ""
+  var format = newFormat()
+  for r in s.runes:
+    if r.isControlChar():
+      s &= edit.term.processFormat(format, colorFormat)
+    else:
+      s &= edit.term.processFormat(format, defaultFormat)
+    s &= r
+  edit.term.write(s)
+
 template kill0(edit: LineEdit, i: int) =
   edit.space(i)
   edit.backward0(i)
@@ -106,7 +114,7 @@ proc space(edit: LineEdit, i: int) =
   edit.term.write(' '.repeat(i))
 
 proc generateOutput*(edit: LineEdit): FixedGrid =
-  result = newFixedGrid(edit.promptw + edit.maxlen)
+  result = newFixedGrid(edit.promptw + edit.maxwidth)
   let os = edit.news.substr(edit.shift, edit.shift + edit.displen)
   var x = 0
   for r in edit.prompt.runes():
@@ -114,8 +122,9 @@ proc generateOutput*(edit: LineEdit): FixedGrid =
     x += r.lwidth()
   if edit.hide:
     for r in os:
-      result[x].str = "*"
-      x += r.lwidth()
+      let w = r.lwidth()
+      result[x].str = '*'.repeat(w)
+      x += w
       if x >= result.width: break
   else:
     for r in os:
@@ -127,10 +136,10 @@ proc getCursorX*(edit: LineEdit): int =
   return edit.promptw + edit.news.lwidth(edit.shift, edit.cursor)
 
 proc redraw(state: LineEdit) =
-  var dispw = state.news.lwidth(state.shift, state.shift + state.displen)
   if state.shift + state.displen > state.news.len:
     state.displen = state.news.len - state.shift
-  while dispw > state.maxlen - 1:
+  var dispw = state.news.lwidth(state.shift, state.shift + state.displen)
+  while dispw > state.maxwidth - 1:
     dispw -= state.news[state.shift + state.displen - 1].lwidth()
     dec state.displen
   state.begin0()
@@ -139,52 +148,55 @@ proc redraw(state: LineEdit) =
     state.printesc('*'.repeat(os.lwidth()))
   else:
     state.printesc(os)
-  state.space(max(state.maxlen - state.minlen - os.lwidth(), 0))
+  state.space(max(state.maxwidth - state.minlen - os.lwidth(), 0))
   state.begin0()
   state.forward0(state.news.lwidth(state.shift, state.cursor))
 
 proc zeroShiftRedraw(state: LineEdit) =
   state.shift = 0
-  state.displen = state.maxlen - 1
+  state.displen = state.news.len
   state.redraw()
 
-proc fullRedraw*(state: LineEdit) =
-  state.displen = state.maxlen - 1
+proc fullRedraw(state: LineEdit) =
+  state.displen = state.news.len
   if state.cursor > state.shift:
     var shiftw = state.news.lwidth(state.shift, state.cursor)
-    while shiftw > state.maxlen - 1:
+    while shiftw > state.maxwidth - 1:
       inc state.shift
       shiftw -= state.news[state.shift].lwidth()
   else:
     state.shift = max(state.cursor - 1, 0)
   state.redraw()
 
-proc insertCharseq(state: LineEdit, cs: var seq[Rune], disallowed: set[char]) =
-  let escNext = state.escNext
-  cs.keepIf((r) => (escNext or not r.isControlChar) and not (r.isAscii and char(r) in disallowed))
-  state.escNext = false
+proc insertCharseq(edit: LineEdit, cs: var seq[Rune]) =
+  let escNext = edit.escNext
+  cs.keepIf((r) => (escNext or not r.isControlChar) and not (r.isAscii and char(r) in edit.disallowed))
+  edit.escNext = false
   if cs.len == 0:
     return
 
-  if state.cursor >= state.news.len and state.news.lwidth(state.shift, state.cursor) + cs.lwidth() < state.displen:
-    state.news &= cs
-    state.cursor += cs.len
-    if state.hide:
-      state.printesc('*'.repeat(cs.lwidth()))
+  if edit.cursor >= edit.news.len and edit.news.lwidth(edit.shift, edit.cursor) + cs.lwidth() < edit.maxwidth:
+    edit.news &= cs
+    edit.cursor += cs.len
+    if edit.hide:
+      edit.printesc('*'.repeat(cs.lwidth()))
     else:
-      state.printesc(cs)
+      edit.printesc(cs)
   else:
-    state.news.insert(cs, state.cursor)
-    state.cursor += cs.len
-    state.fullRedraw()
+    edit.news.insert(cs, edit.cursor)
+    edit.cursor += cs.len
+    edit.fullRedraw()
 
-proc cancel*(edit: LineEdit) {.jsfunc.} =
+proc cancel(edit: LineEdit) {.jsfunc.} =
   edit.state = CANCEL
 
-proc submit*(edit: LineEdit) {.jsfunc.} =
+proc submit(edit: LineEdit) {.jsfunc.} =
+  let s = $edit.news
+  if edit.hist.lines.len == 0 or s != edit.hist.lines[^1]:
+    edit.hist.lines.add(s)
   edit.state = FINISH
 
-proc backspace*(edit: LineEdit) {.jsfunc.} =
+proc backspace(edit: LineEdit) {.jsfunc.} =
   if edit.cursor > 0:
     let w = edit.news[edit.cursor - 1].lwidth()
     edit.news.delete(edit.cursor - 1..edit.cursor - 1)
@@ -198,10 +210,10 @@ proc backspace*(edit: LineEdit) {.jsfunc.} =
 proc write*(edit: LineEdit, s: string): bool {.jsfunc.} =
   if validateUtf8(s) == -1:
     var cs = s.toRunes()
-    edit.insertCharseq(cs, edit.disallowed)
+    edit.insertCharseq(cs)
     return true
 
-proc delete*(edit: LineEdit) {.jsfunc.} =
+proc delete(edit: LineEdit) {.jsfunc.} =
   if edit.cursor >= 0 and edit.cursor < edit.news.len:
     let w = edit.news[edit.cursor].lwidth()
     edit.news.delete(edit.cursor..edit.cursor)
@@ -210,21 +222,21 @@ proc delete*(edit: LineEdit) {.jsfunc.} =
     else:
       edit.fullRedraw()
 
-proc escape*(edit: LineEdit) {.jsfunc.} =
+proc escape(edit: LineEdit) {.jsfunc.} =
   edit.escNext = true
 
-proc clear*(edit: LineEdit) {.jsfunc.} =
+proc clear(edit: LineEdit) {.jsfunc.} =
   if edit.cursor > 0:
     edit.news.delete(0..edit.cursor - 1)
     edit.cursor = 0
     edit.zeroShiftRedraw()
 
-proc kill*(edit: LineEdit) {.jsfunc.} =
+proc kill(edit: LineEdit) {.jsfunc.} =
   if edit.cursor < edit.news.len:
     edit.kill0()
     edit.news.setLen(edit.cursor)
 
-proc backward*(edit: LineEdit) {.jsfunc.} =
+proc backward(edit: LineEdit) {.jsfunc.} =
   if edit.cursor > 0:
     dec edit.cursor
     if edit.cursor > edit.shift or edit.shift == 0:
@@ -232,10 +244,10 @@ proc backward*(edit: LineEdit) {.jsfunc.} =
     else:
       edit.fullRedraw()
 
-proc forward*(edit: LineEdit) {.jsfunc.} =
+proc forward(edit: LineEdit) {.jsfunc.} =
   if edit.cursor < edit.news.len:
     inc edit.cursor
-    if edit.news.lwidth(edit.shift, edit.cursor) < edit.displen:
+    if edit.news.lwidth(edit.shift, edit.cursor) < edit.maxwidth:
       var n = 1
       if edit.news.len > edit.cursor:
         n = edit.news[edit.cursor].lwidth()
@@ -243,7 +255,7 @@ proc forward*(edit: LineEdit) {.jsfunc.} =
     else:
       edit.fullRedraw()
 
-proc prevWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
+proc prevWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
   let oc = edit.cursor
   while edit.cursor > 0:
     dec edit.cursor
@@ -255,8 +267,9 @@ proc prevWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
     else:
       edit.fullRedraw()
 
-proc nextWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
+proc nextWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
   let oc = edit.cursor
+  let ow = edit.news.lwidth(edit.shift, edit.cursor)
   while edit.cursor < edit.news.len:
     inc edit.cursor
     if edit.cursor < edit.news.len:
@@ -264,12 +277,12 @@ proc nextWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
         break
   if edit.cursor != oc:
     let dw = edit.news.lwidth(oc, edit.cursor)
-    if oc + dw - edit.shift < edit.displen:
+    if ow + dw < edit.maxwidth:
       edit.forward0(dw)
     else:
       edit.fullRedraw()
 
-proc clearWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
+proc clearWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
   var i = edit.cursor
   if i > 0:
     # point to the previous character
@@ -284,7 +297,7 @@ proc clearWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
     edit.cursor = i
     edit.fullRedraw()
 
-proc killWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
+proc killWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
   var i = edit.cursor
   if i < edit.news.len and edit.news[i].breaksWord(check):
     inc i
@@ -296,35 +309,49 @@ proc killWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} =
     edit.news.delete(edit.cursor..<i)
     edit.fullRedraw()
 
-proc begin*(edit: LineEdit) {.jsfunc.} =
+proc begin(edit: LineEdit) {.jsfunc.} =
   if edit.cursor > 0:
     if edit.shift == 0:
       edit.backward0(edit.news.lwidth(0, edit.cursor))
+      edit.cursor = 0
     else:
+      edit.cursor = 0
       edit.fullRedraw()
-    edit.cursor = 0
 
-proc `end`*(edit: LineEdit) {.jsfunc.} =
+proc `end`(edit: LineEdit) {.jsfunc.} =
   if edit.cursor < edit.news.len:
-    if edit.news.lwidth(edit.shift, edit.news.len) < edit.maxlen:
+    if edit.news.lwidth(edit.shift, edit.news.len) < edit.maxwidth:
       edit.forward0(edit.news.lwidth(edit.cursor, edit.news.len))
+      edit.cursor = edit.news.len
     else:
+      edit.cursor = edit.news.len
       edit.fullRedraw()
-    edit.cursor = edit.news.len
 
-proc writePrompt*(edit: LineEdit) =
-  edit.printesc(edit.prompt)
+proc prevHist(edit: LineEdit) {.jsfunc.} =
+  if edit.histindex > 0:
+    if edit.news.len > 0:
+      edit.histtmp = $edit.news
+    dec edit.histindex
+    edit.news = edit.hist.lines[edit.histindex].toRunes()
+    edit.end()
+    edit.fullRedraw()
 
-proc writeStart*(edit: LineEdit) =
-  edit.writePrompt()
-  if edit.hide:
-    edit.printesc('*'.repeat(edit.current.lwidth()))
-  else:
-    edit.printesc(edit.current)
+proc nextHist(edit: LineEdit) {.jsfunc.} =
+  if edit.histindex + 1 < edit.hist.lines.len:
+    inc edit.histindex
+    edit.news = edit.hist.lines[edit.histindex].toRunes()
+    edit.end()
+    edit.fullRedraw()
+  elif edit.histindex < edit.hist.lines.len:
+    inc edit.histindex
+    edit.news = edit.histtmp.toRunes()
+    edit.end()
+    edit.fullRedraw()
+    edit.histtmp = ""
 
 proc readLine*(prompt: string, termwidth: int, current = "",
                disallowed: set[char] = {}, hide = false,
-               term: Terminal): LineEdit =
+               term: Terminal, hist: LineHistory): LineEdit =
   result = LineEdit(
     prompt: prompt,
     promptw: prompt.lwidth(),
@@ -336,8 +363,10 @@ proc readLine*(prompt: string, termwidth: int, current = "",
     term: term
   )
   result.cursor = result.news.lwidth()
-  result.maxlen = termwidth - result.promptw
-  result.displen = result.maxlen - 1
+  result.maxwidth = termwidth - result.promptw
+  result.displen = result.cursor
+  result.hist = hist
+  result.histindex = result.hist.lines.len
 
 proc addLineEditModule*(ctx: JSContext) =
   ctx.registerType(LineEdit)