about summary refs log tree commit diff stats
path: root/src/buffer
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-11-28 19:52:10 +0100
committerbptato <nincsnevem662@gmail.com>2022-11-28 23:00:06 +0100
commiteb2e57c97eb67eec19f068e294a8f6d1375c82f5 (patch)
tree87156c515f6ee9a63f58dc080184bd3127ce6836 /src/buffer
parent8af10b8b74fd29fe4c9debcd5cbecfaddf53a7b5 (diff)
downloadchawan-eb2e57c97eb67eec19f068e294a8f6d1375c82f5.tar.gz
Add textarea
Editing is implemented using an external editor (like vi).
Diffstat (limited to 'src/buffer')
-rw-r--r--src/buffer/buffer.nim98
-rw-r--r--src/buffer/container.nim49
2 files changed, 89 insertions, 58 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim
index 2bc67d7c..d7061ce8 100644
--- a/src/buffer/buffer.nim
+++ b/src/buffer/buffer.nim
@@ -17,7 +17,7 @@ import css/cssparser
 import css/mediaquery
 import css/sheet
 import css/stylednode
-import config/bufferconfig
+import config/config
 import html/dom
 import html/tags
 import html/htmlparser
@@ -56,7 +56,6 @@ type
     lasttimeout: int
     timeout: int
     readbufsize: int
-    input: HTMLInputElement
     contenttype: string
     lines: FlexibleGrid
     rendered: bool
@@ -239,7 +238,7 @@ func getLink(node: StyledNode): HTMLAnchorElement =
   #TODO ::before links?
 
 const ClickableElements = {
-  TAG_A, TAG_INPUT, TAG_OPTION, TAG_BUTTON
+  TAG_A, TAG_INPUT, TAG_OPTION, TAG_BUTTON, TAG_TEXTAREA
 }
 
 func getClickable(styledNode: StyledNode): Element =
@@ -267,6 +266,7 @@ func cursorBytes(buffer: Buffer, y: int, cc: int): int =
   return i
 
 proc findPrevLink*(buffer: Buffer, cursorx, cursory: int): tuple[x, y: int] {.proxy.} =
+  if cursory >= buffer.lines.len: return (-1, -1)
   let line = buffer.lines[cursory]
   var i = line.findFormatN(cursorx) - 1
   var link: Element = nil
@@ -324,6 +324,7 @@ proc findPrevLink*(buffer: Buffer, cursorx, cursory: int): tuple[x, y: int] {.pr
   return (-1, -1)
 
 proc findNextLink*(buffer: Buffer, cursorx, cursory: int): tuple[x, y: int] {.proxy.} =
+  if cursory >= buffer.lines.len: return (-1, -1)
   let line = buffer.lines[cursory]
   var i = line.findFormatN(cursorx) - 1
   var link: Element = nil
@@ -350,6 +351,7 @@ proc findNextLink*(buffer: Buffer, cursorx, cursory: int): tuple[x, y: int] {.pr
   return (-1, -1)
 
 proc findPrevMatch*(buffer: Buffer, regex: Regex, cursorx, cursory: int, wrap: bool): BufferMatch {.proxy.} =
+  if cursory >= buffer.lines.len: return
   template return_if_match =
     if res.success and res.captures.len > 0:
       let cap = res.captures[^1]
@@ -377,6 +379,7 @@ proc findPrevMatch*(buffer: Buffer, regex: Regex, cursorx, cursory: int, wrap: b
     dec y
 
 proc findNextMatch*(buffer: Buffer, regex: Regex, cursorx, cursory: int, wrap: bool): BufferMatch {.proxy.} =
+  if cursory >= buffer.lines.len: return
   template return_if_match =
     if res.success and res.captures.len > 0:
       let cap = res.captures[0]
@@ -471,6 +474,7 @@ proc updateHover*(buffer: Buffer, cursorx, cursory: int): UpdateHoverResult {.pr
   buffer.prevnode = thisnode
 
 proc loadResource(buffer: Buffer, document: Document, elem: HTMLLinkElement) =
+  if elem.href == "": return
   let url = parseUrl(elem.href, document.location.some)
   if url.isSome:
     let url = url.get
@@ -514,8 +518,7 @@ proc setupSource(buffer: Buffer): ConnectResult =
   buffer.location = source.location
   case source.t
   of CLONE:
-    buffer.istream = connectSocketStream(source.clonepid)
-    SocketStream(buffer.istream).source.getFd().setBlocking(false)
+    buffer.istream = connectSocketStream(source.clonepid, blocking = false)
     if buffer.istream == nil:
       result.code = -2
       return
@@ -574,7 +577,7 @@ proc load*(buffer: Buffer): tuple[atend: bool, lines, bytes: int] {.proxy.} =
   var s = newString(buffer.readbufsize)
   try:
     buffer.istream.readStr(buffer.readbufsize, s)
-    result = (s.len < buffer.readbufsize, buffer.lines.len, bytes)
+    result = (s.len == 0, buffer.lines.len, bytes)
     if buffer.readbufsize < BufferSize:
       buffer.readbufsize = min(BufferSize, buffer.readbufsize * 2)
   except IOError:
@@ -671,12 +674,14 @@ proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encodin
         "UTF-8"
       entrylist.add((name, charset))
     else:
-      if field.tagType == TAG_INPUT:
+      case field.tagType
+      of TAG_INPUT:
         entrylist.add((name, HTMLInputElement(field).value))
-      elif field.tagType == TAG_BUTTON:
+      of TAG_BUTTON:
         entrylist.add((name, HTMLButtonElement(field).value))
-      else:
-        assert false
+      of TAG_TEXTAREA:
+        entrylist.add((name, HTMLTextAreaElement(field).value))
+      else: assert false, "Tag type " & $field.tagType & " not accounted for in constructEntryList"
     if field.tagType == TAG_TEXTAREA or
         field.tagType == TAG_INPUT and HTMLInputElement(field).inputType in {INPUT_TEXT, INPUT_SEARCH}:
       if field.attr("dirname") != "":
@@ -803,39 +808,46 @@ type ReadSuccessResult* = object
   open*: Option[Request]
   repaint*: bool
 
+proc implicitSubmit(buffer: Buffer, input: HTMLInputElement): Option[Request] =
+  if input.form != nil and input.form.canSubmitImplicitly():
+    return submitForm(input.form, input.form)
+
 proc readSuccess*(buffer: Buffer, s: string): ReadSuccessResult {.proxy.} =
-  if buffer.input != nil:
-    let input = buffer.input
-    case input.inputType
-    of INPUT_SEARCH:
-      input.value = s
-      input.invalid = true
-      buffer.do_reshape()
-      result.repaint = true
-      if input.form != nil:
-        let submitaction = submitForm(input.form, input)
-        if submitaction.isSome:
-          result.open = submitaction
-    of INPUT_TEXT, INPUT_PASSWORD:
-      input.value = s
-      input.invalid = true
-      buffer.do_reshape()
-      result.repaint = true
-    of INPUT_FILE:
-      let cdir = parseUrl("file://" & getCurrentDir() & DirSep)
-      let path = parseUrl(s, cdir)
-      if path.issome:
-        input.file = path
+  if buffer.document.focus != nil:
+    case buffer.document.focus.tagType
+    of TAG_INPUT:
+      let input = HTMLInputElement(buffer.document.focus)
+      case input.inputType
+      of INPUT_SEARCH, INPUT_TEXT, INPUT_PASSWORD:
+        input.value = s
         input.invalid = true
         buffer.do_reshape()
         result.repaint = true
+        result.open = buffer.implicitSubmit(input)
+      of INPUT_FILE:
+        let cdir = parseUrl("file://" & getCurrentDir() & DirSep)
+        let path = parseUrl(s, cdir)
+        if path.issome:
+          input.file = path
+          input.invalid = true
+          buffer.do_reshape()
+          result.repaint = true
+          result.open = buffer.implicitSubmit(input)
+      else: discard
+    of TAG_TEXTAREA:
+      let textarea = HTMLTextAreaElement(buffer.document.focus)
+      textarea.value = s
+      textarea.invalid = true
+      buffer.do_reshape()
+      result.repaint = true
     else: discard
-    buffer.input = nil
+    buffer.restore_focus
 
 type ReadLineResult* = object
   prompt*: string
   value*: string
   hide*: bool
+  area*: bool
 
 type ClickResult* = object
   open*: Option[Request]
@@ -848,6 +860,7 @@ proc click*(buffer: Buffer, cursorx, cursory: int): ClickResult {.proxy.} =
     case clickable.tagType
     of TAG_SELECT:
       buffer.set_focus clickable
+      result.repaint = true
     of TAG_A:
       buffer.restore_focus
       let url = parseUrl(HTMLAnchorElement(clickable).href, clickable.document.baseUrl.some)
@@ -877,24 +890,32 @@ proc click*(buffer: Buffer, cursorx, cursory: int): ClickResult {.proxy.} =
           result.repaint = true
           buffer.do_reshape()
         of BUTTON_BUTTON: discard
+    of TAG_TEXTAREA:
+      buffer.set_focus clickable
+      let textarea = HTMLTextAreaElement(clickable)
+      result.readline = some(ReadLineResult(
+        value: textarea.value,
+        area: true
+      ))
     of TAG_INPUT:
       buffer.restore_focus
       let input = HTMLInputElement(clickable)
       case input.inputType
       of INPUT_SEARCH:
-        buffer.input = input
+        buffer.set_focus input
         result.readline = some(ReadLineResult(
           prompt: "SEARCH: ",
           value: input.value
         ))
       of INPUT_TEXT, INPUT_PASSWORD:
-        buffer.input = input
+        buffer.set_focus input
         result.readline = some(ReadLineResult(
           prompt: "TEXT: ",
           value: input.value,
           hide: input.inputType == INPUT_PASSWORD
         ))
       of INPUT_FILE:
+        buffer.set_focus input
         var path = if input.file.issome:
           input.file.get.path.serialize_unicode()
         else:
@@ -930,12 +951,12 @@ proc click*(buffer: Buffer, cursorx, cursory: int): ClickResult {.proxy.} =
       buffer.restore_focus
 
 proc readCanceled*(buffer: Buffer) {.proxy.} =
-  buffer.input = nil
+  buffer.restore_focus
 
 proc findAnchor*(buffer: Buffer, anchor: string): bool {.proxy.} =
   return buffer.document != nil and buffer.document.getElementById(anchor) != nil
 
-proc getLines*(buffer: Buffer, w: Slice[int]): seq[SimpleFlexibleLine] {.proxy.} =
+proc getLines*(buffer: Buffer, w: Slice[int]): tuple[numLines: int, lines: seq[SimpleFlexibleLine]] {.proxy.} =
   var w = w
   if w.b < 0 or w.b > buffer.lines.high:
     w.b = buffer.lines.high
@@ -944,7 +965,8 @@ proc getLines*(buffer: Buffer, w: Slice[int]): seq[SimpleFlexibleLine] {.proxy.}
     var line = SimpleFlexibleLine(str: buffer.lines[y].str)
     for f in buffer.lines[y].formats:
       line.formats.add(SimpleFormatCell(format: f.format, pos: f.pos))
-    result.add(line)
+    result.lines.add(line)
+  result.numLines = buffer.lines.len
 
 proc passFd*(buffer: Buffer) {.proxy.} =
   let fd = SocketStream(buffer.pistream).recvFileHandle()
diff --git a/src/buffer/container.nim b/src/buffer/container.nim
index 65b1ae59..9a0ff802 100644
--- a/src/buffer/container.nim
+++ b/src/buffer/container.nim
@@ -8,7 +8,6 @@ when defined(posix):
 
 import buffer/buffer
 import buffer/cell
-import config/bufferconfig
 import config/config
 import io/request
 import io/window
@@ -33,7 +32,7 @@ type
 
   ContainerEventType* = enum
     NO_EVENT, FAIL, SUCCESS, NEEDS_AUTH, REDIRECT, ANCHOR, NO_ANCHOR, UPDATE,
-    READ_LINE, OPEN, INVALID_COMMAND, STATUS, ALERT
+    READ_LINE, READ_AREA, OPEN, INVALID_COMMAND, STATUS, ALERT
 
   ContainerEvent* = object
     case t*: ContainerEventType
@@ -41,6 +40,8 @@ type
       prompt*: string
       value*: string
       password*: bool
+    of READ_AREA:
+      tvalue*: string
     of OPEN:
       request*: Request
     of ANCHOR, NO_ANCHOR:
@@ -71,7 +72,7 @@ type
     bpos: seq[CursorPosition]
     highlights: seq[Highlight]
     parent*: Container
-    process*: Pid
+    process* {.jsget.}: Pid
     loadinfo*: string
     lines: SimpleFlexibleGrid
     lineshift: int
@@ -93,7 +94,7 @@ proc newBuffer*(dispatcher: Dispatcher, config: Config, source: BufferSource, ti
   let istream = dispatcher.forkserver.istream
   ostream.swrite(FORK_BUFFER)
   ostream.swrite(source)
-  ostream.swrite(config.loadBufferConfig())
+  ostream.swrite(config.getBufferConfig())
   ostream.swrite(attrs)
   ostream.swrite(dispatcher.mainproc)
   ostream.flush()
@@ -252,13 +253,17 @@ proc triggerEvent(container: Container, t: ContainerEventType) =
 
 proc updateCursor(container: Container)
 
+proc setNumLines(container: Container, lines: int) =
+  container.numLines = lines
+  container.updateCursor()
+
 proc requestLines*(container: Container, w = container.lineWindow) =
-  container.iface.getLines(w).then(proc(res: seq[SimpleFlexibleLine]) =
+  container.iface.getLines(w).then(proc(res: tuple[numLines: int, lines: seq[SimpleFlexibleLine]]) =
     container.lines.setLen(w.len)
     container.lineshift = w.a
-    for y in 0 ..< min(res.len, w.len):
-      container.lines[y] = res[y]
-    container.updateCursor()
+    for y in 0 ..< min(res.lines.len, w.len):
+      container.lines[y] = res.lines[y]
+    container.setNumLines(res.numLines)
     container.redraw = true
     let cw = container.fromy ..< container.fromy + container.height
     if w.a in cw or w.b in cw or cw.a in w or cw.b in w:
@@ -544,8 +549,8 @@ proc cursorNextMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.}
   container.iface
     .findNextMatch(regex, container.cursorx, container.cursory, wrap)
     .then(proc(res: BufferMatch) =
+      container.setCursorXY(res.x, res.y)
       if container.hlon:
-        container.setCursorXY(res.x, res.y)
         container.clearSearchHighlights()
         let ex = res.x + res.str.width() - 1
         let hl = Highlight(x: res.x, y: res.y, endx: ex, endy: res.y, clear: true)
@@ -556,8 +561,8 @@ proc cursorPrevMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.}
   container.iface
     .findPrevMatch(regex, container.cursorx, container.cursory, wrap)
     .then(proc(res: BufferMatch) =
+      container.setCursorXY(res.x, res.y)
       if container.hlon:
-        container.setCursorXY(res.x, res.y)
         container.clearSearchHighlights()
         let ex = res.x + res.str.width() - 1
         let hl = Highlight(x: res.x, y: res.y, endx: ex, endy: res.y, clear: true)
@@ -568,10 +573,6 @@ proc setLoadInfo(container: Container, msg: string) =
   container.loadinfo = msg
   container.triggerEvent(STATUS)
 
-proc setNumLines(container: Container, lines: int) =
-  container.numLines = lines
-  container.updateCursor()
-
 proc alert(container: Container, msg: string) =
   container.triggerEvent(ContainerEvent(t: ALERT, msg: msg))
 
@@ -677,12 +678,20 @@ proc click*(container: Container) {.jsfunc.} =
       container.triggerEvent(ContainerEvent(t: OPEN, request: res.open.get))
     if res.readline.isSome:
       let rl = res.readline.get
-      container.triggerEvent(
-        ContainerEvent(
-          t: READ_LINE,
-          prompt: rl.prompt,
-          value: rl.value,
-          password: rl.hide)))
+      if rl.area:
+        container.triggerEvent(
+          ContainerEvent(
+            t: READ_AREA,
+            tvalue: rl.value
+          ))
+      else:
+        container.triggerEvent(
+          ContainerEvent(
+            t: READ_LINE,
+            prompt: rl.prompt,
+            value: rl.value,
+            password: rl.hide
+          )))
 
 proc windowChange*(container: Container, attrs: WindowAttributes) =
   container.attrs = attrs