about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-09-22 13:14:36 +0200
committerbptato <nincsnevem662@gmail.com>2024-09-22 13:37:13 +0200
commite23fa780cf2fff7146efcd64b2806ce428858b80 (patch)
tree1c27dc0a50c03bc1d4348d9b96328009e0adce8f
parent9386feca5e39b89676dd410f6331553c37434239 (diff)
downloadchawan-e23fa780cf2fff7146efcd64b2806ce428858b80.tar.gz
pager: improve hover text handling
* align status truncating behavior with w3m (not exactly, clipping
  is still different, but this should be fine for now)
* add "su" for "show last alert"
	- w3m's solution here is to scroll one char at a time with
	  "u", but that's extremely annoying to use. We already have a
	  line editor that can navigate lines, so reuse that instead.
* fix peekCursor showing empty text
* update todo
-rw-r--r--doc/api.md5
-rw-r--r--doc/config.md33
-rw-r--r--res/chawan.html13
-rw-r--r--res/config.toml4
-rw-r--r--src/local/container.nim3
-rw-r--r--src/local/lineedit.nim3
-rw-r--r--src/local/pager.nim115
-rw-r--r--todo27
8 files changed, 136 insertions, 67 deletions
diff --git a/doc/api.md b/doc/api.md
index 004b52af..ec41f390 100644
--- a/doc/api.md
+++ b/doc/api.md
@@ -223,6 +223,11 @@ press again -> URL)</td>
 </tr>
 
 <tr>
+<td>`showFullAlert()`</td>
+<td>Show the last alert inside the line editor.</td>
+</tr>
+
+<tr>
 <td>`ask(prompt)`</td>
 <td>Ask the user for confirmation. Returns a promise which resolves to a
 boolean value indicating whether the user responded with yes.<br>
diff --git a/doc/config.md b/doc/config.md
index d4253b4e..13afec04 100644
--- a/doc/config.md
+++ b/doc/config.md
@@ -484,7 +484,7 @@ images.
 
 <tr>
 <td>sixel-colors</td>
-<td>"auto" / 3..65535</td>
+<td>"auto" / 2..65535</td>
 <td>Only applies when `display.image-mode="sixel"`. Setting a number
 overrides the number of sixel color registers reported by the terminal,
 while "auto" leaves color detection to Chawan.<br>
@@ -888,16 +888,19 @@ Examples:
 <table>
 
 <tr>
+<th>Default key</th>
 <th>Name</th>
 <th>Function</th>
 </tr>
 
 <tr>
+<td><kbd>q</kbd></td>
 <td>`cmd.pager.quit`</td>
 <td>Exit the browser.</td>
 </tr>
 
 <tr>
+<td><kbd>C-z</kbd></td>
 <td>`cmd.pager.suspend`</td>
 <td>Temporarily suspend the browser<br>
 Note: this also suspends e.g. buffer processes or CGI scripts. So if you are
@@ -905,56 +908,67 @@ downloading something, that will be delayed until you restart the process.</td>
 </tr>
 
 <tr>
+<td><kbd>C-l</kbd></td>
 <td>`cmd.pager.load`</td>
 <td>Open the current address in the URL bar.</td>
 </tr>
 
 <tr>
+<td><kbd>C-k</kbd></td>
 <td>`cmd.pager.webSearch`</td>
 <td>Open the URL bar with an arbitrary search engine. At the moment, this is
 DuckDuckGo Lite, but this may change in the future.</td>
 </tr>
 
 <tr>
+<td><kbd>M-u</kbd></td>
 <td>`cmd.pager.dupeBuffer`</td>
 <td>Duplicate the current buffer by loading its source to a new buffer.</td>
 </tr>
 
 <tr>
+<td><kbd>U</kbd></td>
 <td>`cmd.pager.reloadBuffer`</td>
 <td>Open a new buffer with the current buffer's URL, replacing the current
 buffer.</td>
 </tr>
 
 <tr>
+<td><kbd>C-g</kbd></td>
 <td>`cmd.pager.lineInfo`</td>
 <td>Display information about the current line on the status line.</td>
 </tr>
 
 <tr>
+<td><kbd>&bsol;</kbd></td>
 <td>`cmd.pager.toggleSource`</td>
 <td>If viewing an HTML buffer, open a new buffer with its source. Otherwise,
 open the current buffer's contents as HTML.</td>
 </tr>
 
 <tr>
+<td><kbd>D</kbd></td>
 <td>`cmd.pager.discardBuffer`</td>
 <td>Discard the current buffer, and move back to the previous/next buffer
 depending on what the previously viewed buffer was.</td>
 </tr>
 
 <tr>
+<td><kbd>d,</kbd>, <kbd>d.</kbd></td>
 <td>`cmd.pager.discardBufferPrev`, `cmd.pager.discardBufferNext`</td>
 <td>Discard the current buffer, and move back to the previous/next buffer, or
 open the link under the cursor.</td>
 </tr>
 
 <tr>
+<td><kbd>M-d</kbd></td>
 <td>`cmd.pager.discardTree`</td>
 <td>Discard all child buffers of the current buffer.</td>
 </tr>
 
 <tr>
+<td><kbd>.</kbd>, <kbd>,</kbd>, <kbd>M-,</kbd>, <kbd>M-.</kbd>,
+<kbd>M-/</kbd></td>
 <td>`cmd.pager.nextBuffer`, `cmd.pager.prevBuffer`,
 `cmd.pager.prevSiblingBuffer`, `cmd.pager.nextSiblingBufer`,
 `cmd.pager.parentBuffer`</td>
@@ -969,33 +983,39 @@ opened from, even if e.g. the user returns and opens another page "in between".
 </tr>
 
 <tr>
+<td><kbd>M-c</kbd></td>
 <td>`cmd.pager.enterCommand`</td>
 <td>Directly enter a JavaScript command. Note that this interacts with
 the pager, not the website being displayed.</td>
 </tr>
 
 <tr>
+<td>None</td>
 <td>`cmd.pager.searchForward`, `cmd.pager.searchBackward`</td>
 <td>Search for a string in the current buffer, forwards or backwards.</td>
 </tr>
 
 <tr>
+<td><kbd>/</kbd>, <kbd>?</kbd></td>
 <td>`cmd.pager.isearchForward`, `cmd.pager.searchBackward`</td>
 <td>Incremental-search for a string, highlighting the first result, forwards or
 backwards.</td>
 </tr>
 
 <tr>
+<td><kbd>n</kbd>, <kbd>N</kbd></td>
 <td>`cmd.pager.searchNext`, `cmd.pager.searchPrev`</td>
 <td>Jump to the nth (or if unspecified, first) next/previous search result.</td>
 </tr>
 
 <tr>
+<td><kbd>c</kbd></td>
 <td>`cmd.pager.peek`</td>
 <td>Display a message of the current buffer's URL on the status line.</td>
 </tr>
 
 <tr>
+<td><kbd>u</kbd></td>
 <td>`cmd.pager.peekCursor`</td>
 <td>Display a message of the URL or title under the cursor on the status line.
 Multiple calls allow cycling through the two. (i.e. by default, press u once ->
@@ -1003,21 +1023,32 @@ title, press again -> URL)</td>
 </tr>
 
 <tr>
+<td><kbd>su</kbd></td>
+<td>`cmd.pager.showFullAlert`</td>
+<td>Show the last alert inside the line editor. You can also view previous
+ones using C-p or C-n.</td>
+</tr>
+
+<tr>
+<td><kbd>M-y</kbd></td>
 <td>`cmd.pager.copyURL`</td>
 <td>Copy the current buffer's URL to the system clipboard.</td>
 </tr>
 
 <tr>
+<td><kbd>yu</kbd></td>
 <td>`cmd.pager.copyCursorLink`</td>
 <td>Copy the link under the cursor to the system clipboard.</td>
 </tr>
 
 <tr>
+<td><kbd>yI</kbd></td>
 <td>`cmd.pager.copyCursorImage`</td>
 <td>Copy the URL of the image under the cursor to the system clipboard.</td>
 </tr>
 
 <tr>
+<td><kbd>M-p</kbd></td>
 <td>`cmd.pager.gotoClipboardURL`</td>
 <td>Go to the URL currently on the clipboard.</td>
 </tr>
diff --git a/res/chawan.html b/res/chawan.html
index 06d0597a..72bd041f 100644
--- a/res/chawan.html
+++ b/res/chawan.html
@@ -24,15 +24,12 @@ kbd {
 <center><h1>Welcome to Chawan!</h1></center>
 <pre class=logo>
  _......................._
-fr.,~.~,,~~.~~~,~,~~~~,,.bl
-I!www~www~www~www~www~www!ds
+fr.,~.~,,~~.~~~,~,~~~~,~.l\
+I!www~www~www~www~www~www!dp
 lCHCHCHCHCHCHCHCHCHCHCHCHCp
- kCHCHCHCHCHCHCHCHCHCHCHCp
   sAAAAAAAAAAAAAAAAAAAAAp
-   l<u>S</u>WWWWWWWWWWWWWWWWW<u>Z</u>i
-     lSAAAAAAAAAAAAAZ/
-       \<u>ZM</u>NNNNNNN<u>MZ</u>/
-          \<u>HHHHH</u>/
+    SWWWWWWWWWWWWWWWWWZ
+      \Z<u>MNMNMNMNMNM</u>Z/
 </pre>
 <p>
 This is the default visual home page. You can change it in your configuration
@@ -62,6 +59,8 @@ came from
 <li><kbd>d,</kbd>, <kbd>d.</kbd>: discard (delete) current
 buffer, then move to previous/next buffer
 <li><kbd>M-y</kbd>: copy current buffer's URL to clipboard (needs xsel)
+<li><kbd>u</kbd>: view link/title text currently under the cursor
+<li><kbd>su</kbd>: show last alert message
 <li><kbd>yu</kbd>: copy the link currently under the cursor to clipboard (needs
 xsel)
 <li><kbd>yI</kbd>: copy the image link currently under the cursor to clipboard
diff --git a/res/config.toml b/res/config.toml
index 1a7318a0..6cd83253 100644
--- a/res/config.toml
+++ b/res/config.toml
@@ -47,7 +47,7 @@ gotoClipboardURL = '''
 }
 '''
 peek = '() => pager.alert(pager.url)'
-peekCursor = '() => pager.peekCursor()'
+peekCursor = 'n => pager.peekCursor(n)'
 toggleWrap = '''
 () => {
 	config.search.wrap = !config.search.wrap;
@@ -85,6 +85,7 @@ toggleCommandMode = '''
 		console.hide();
 }
 '''
+showFullAlert = '() => pager.showFullAlert()'
 
 [cmd.buffer]
 cursorLeft = 'n => pager.cursorLeft(n)'
@@ -420,6 +421,7 @@ n = 'cmd.pager.searchNext'
 N = 'cmd.pager.searchPrev'
 c = 'cmd.pager.peek'
 u = 'cmd.pager.peekCursor'
+su = 'cmd.pager.showFullAlert'
 C-w = 'cmd.pager.toggleWrap'
 M-y = 'cmd.pager.copyURL'
 yc = 'pager.alert("Please use `yu` to copy URLs")'
diff --git a/src/local/container.nim b/src/local/container.nim
index 6310c8d6..8f295ad9 100644
--- a/src/local/container.nim
+++ b/src/local/container.nim
@@ -1930,7 +1930,8 @@ proc peekCursor(container: Container) {.jsfunc.} =
       p = low(HoverType)
     if container.hoverText[p] != "" or p == container.lastPeek:
       break
-  container.alert($p & ": " & container.hoverText[p])
+  if container.hoverText[p] != "":
+    container.alert($p & ": " & container.hoverText[p])
   container.lastPeek = p
 
 func hoverLink(container: Container): string {.jsfget.} =
diff --git a/src/local/lineedit.nim b/src/local/lineedit.nim
index edb6ee09..2f998b84 100644
--- a/src/local/lineedit.nim
+++ b/src/local/lineedit.nim
@@ -18,7 +18,7 @@ type
     lesEdit, lesFinish, lesCancel
 
   LineHistory* = ref object
-    lines: seq[string]
+    lines*: seq[string]
 
   LineEdit* = ref object
     news*: string
@@ -299,6 +299,7 @@ proc nextHist(edit: LineEdit) {.jsfunc.} =
     edit.begin()
     edit.end()
     edit.histtmp = ""
+    edit.redraw = true
 
 proc windowChange*(edit: LineEdit; attrs: WindowAttributes) =
   edit.maxwidth = attrs.width - edit.promptw - 1
diff --git a/src/local/pager.nim b/src/local/pager.nim
index 0bd60abb..fa205596 100644
--- a/src/local/pager.nim
+++ b/src/local/pager.nim
@@ -68,6 +68,7 @@ type
     lmGotoLine = "Goto line: "
     lmDownload = "(Download)Save file to: "
     lmBufferFile = "(Upload)Filename: "
+    lmAlert = "Alert: "
 
   # fdin is the original fd; fdout may be the same, or different if mailcap
   # is used.
@@ -129,15 +130,15 @@ type
     devRandom: PosixStream
     display: Surface
     forkserver*: ForkServer
-    formRequestMap*: Table[string, FormRequestType]
     hasload*: bool # has a page been successfully loaded since startup?
     inputBuffer*: string # currently uninterpreted characters
     iregex: Result[Regex, string]
     isearchpromise: EmptyPromise
     jsctx: JSContext
+    lastAlert: string # last alert seen by the user
     lineData: LineData
     lineedit*: LineEdit
-    linehist: array[LineMode, LineHistory]
+    lineHist: array[LineMode, LineHistory]
     linemode: LineMode
     loader*: FileLoader
     luctx: LUContext
@@ -160,6 +161,7 @@ jsDestructor(Pager)
 
 # Forward declarations
 proc alert*(pager: Pager; msg: string)
+proc getLineHist(pager: Pager; mode: LineMode): LineHistory
 
 template attrs(pager: Pager): WindowAttributes =
   pager.term.attrs
@@ -266,9 +268,9 @@ proc searchPrev(pager: Pager; n = 1) {.jsfunc.} =
     pager.alert("No previous regular expression")
 
 proc getLineHist(pager: Pager; mode: LineMode): LineHistory =
-  if pager.linehist[mode] == nil:
-    pager.linehist[mode] = newLineHistory()
-  return pager.linehist[mode]
+  if pager.lineHist[mode] == nil:
+    pager.lineHist[mode] = newLineHistory()
+  return pager.lineHist[mode]
 
 proc setLineEdit(pager: Pager; mode: LineMode; current = ""; hide = false;
     extraPrompt = "") =
@@ -279,6 +281,11 @@ proc setLineEdit(pager: Pager; mode: LineMode; current = ""; hide = false;
     {}, hide, hist, pager.luctx)
   pager.linemode = mode
 
+# Reuse the line editor as an alert message viewer.
+proc showFullAlert(pager: Pager) {.jsfunc.} =
+  if pager.lastAlert != "":
+    pager.setLineEdit(lmAlert, pager.lastAlert)
+
 proc clearLineEdit(pager: Pager) =
   pager.lineedit = nil
   if pager.term.isatty() and pager.config.input.use_mouse:
@@ -368,38 +375,41 @@ proc launchPager*(pager: Pager; istream: PosixStream; selector: Selector[int]) =
 proc buffer(pager: Pager): Container {.jsfget, inline.} =
   return pager.container
 
-# Note: this function does not work correctly if start < i of last written char
+# Note: this function does not work correctly if start < x of last written char
 proc writeStatusMessage(pager: Pager; str: string; format = Format();
-    start = 0; maxwidth = -1; clip = '$'): int {.discardable.} =
+    start = 0; maxwidth = -1; clip = '$'): int =
   var maxwidth = maxwidth
   if maxwidth == -1:
     maxwidth = pager.status.grid.len
-  var i = start
+  var x = start
   let e = min(start + maxwidth, pager.status.grid.width)
-  if i >= e:
-    return i
+  if x >= e:
+    return x
   pager.status.redraw = true
+  var lx = 0
   for u in str.points:
     let w = u.width()
-    if i + w >= e:
-      pager.status.grid[i].format = format
-      pager.status.grid[i].str = $clip
-      inc i # Note: we assume `clip' is 1 cell wide
+    if x + w > e: # clip if we overflow (but not on exact fit)
+      if lx < e:
+        pager.status.grid[lx].format = format
+        pager.status.grid[lx].str = $clip
+      x = lx + 1 # clip must be 1 cell wide
       break
     if u.isControlChar():
-      pager.status.grid[i].str = "^"
-      pager.status.grid[i + 1].str = $getControlLetter(char(u))
-      pager.status.grid[i + 1].format = format
+      pager.status.grid[x].str = "^"
+      pager.status.grid[x + 1].str = $getControlLetter(char(u))
+      pager.status.grid[x + 1].format = format
     else:
-      pager.status.grid[i].str = u.toUTF8()
-    pager.status.grid[i].format = format
-    i += w
-  result = i
+      pager.status.grid[x].str = u.toUTF8()
+    pager.status.grid[x].format = format
+    lx = x
+    x += w
+  result = x
   var def = Format()
-  while i < e:
-    pager.status.grid[i].str = ""
-    pager.status.grid[i].format = def
-    inc i
+  while x < e:
+    pager.status.grid[x].str = ""
+    pager.status.grid[x].format = def
+    inc x
 
 # Note: should only be called directly after user interaction.
 proc refreshStatusMsg*(pager: Pager) =
@@ -407,29 +417,49 @@ proc refreshStatusMsg*(pager: Pager) =
   if container == nil: return
   if pager.askpromise != nil: return
   if pager.precnum != 0:
-    pager.writeStatusMessage($pager.precnum & pager.inputBuffer)
+    discard pager.writeStatusMessage($pager.precnum & pager.inputBuffer)
   elif pager.inputBuffer != "":
-    pager.writeStatusMessage(pager.inputBuffer)
+    discard pager.writeStatusMessage(pager.inputBuffer)
   elif pager.alerts.len > 0:
     pager.alertState = pasAlertOn
-    pager.writeStatusMessage(pager.alerts[0])
+    discard pager.writeStatusMessage(pager.alerts[0])
+    # save to alert history
+    if pager.lastAlert != "":
+      let hist = pager.getLineHist(lmAlert)
+      if hist.lines.len == 0 or hist.lines[^1] != pager.lastAlert:
+        if hist.lines.len > 19:
+          hist.lines.delete(0)
+        hist.lines.add(move(pager.lastAlert))
+    pager.lastAlert = move(pager.alerts[0])
     pager.alerts.delete(0)
   else:
     var format = Format(flags: {ffReverse})
     pager.alertState = pasNormal
     container.clearHover()
-    var msg = $(container.cursory + 1) & "/" & $container.numLines & " (" &
-              $container.atPercentOf() & "%)"
-    let mw = pager.writeStatusMessage(msg, format)
-    let title = " <" & container.getTitle() & ">"
+    var msg = $(container.cursory + 1) & "/" & $container.numLines &
+      " (" & $container.atPercentOf() & "%)" &
+      " <" & container.getTitle()
     let hover = container.getHoverText()
-    if hover.len == 0:
-      pager.writeStatusMessage(title, format, mw)
-    else:
-      let hover2 = " " & hover
-      let maxwidth = pager.status.grid.width - hover2.width() - mw
-      let tw = pager.writeStatusMessage(title, format, mw, maxwidth, '>')
-      pager.writeStatusMessage(hover2, format, tw)
+    let sl = hover.notwidth()
+    var l = 0
+    var i = 0
+    var maxw = pager.status.grid.width - 1 # -1 for '>'
+    if sl > 0:
+      maxw -= 1 # plus one blank
+    while i < msg.len:
+      let pi = i
+      let u = msg.nextUTF8(i)
+      l += u.width()
+      if l + sl >= maxw:
+        i = pi
+        break
+    msg.setLen(i)
+    if i > 0:
+      msg &= ">"
+      if sl > 0:
+        msg &= ' '
+    msg &= hover
+    discard pager.writeStatusMessage(msg, format)
 
 # Call refreshStatusMsg if no alert is being displayed on the screen.
 # Alerts take precedence over load info, but load info is preserved when no
@@ -671,7 +701,7 @@ proc onSetLoadInfo(pager: Pager; container: Container) =
     if container.loadinfo == "":
       pager.alertState = pasNormal
     else:
-      pager.writeStatusMessage(container.loadinfo)
+      discard pager.writeStatusMessage(container.loadinfo)
       pager.alertState = pasLoadInfo
 
 proc newContainer(pager: Pager; bufferConfig: BufferConfig;
@@ -897,7 +927,8 @@ proc nextSiblingBuffer(pager: Pager): bool {.jsfunc.} =
   return true
 
 proc alert*(pager: Pager; msg: string) {.jsfunc.} =
-  pager.alerts.add(msg)
+  if msg != "":
+    pager.alerts.add(msg)
 
 # replace target with container in the tree
 proc replace*(pager: Pager; target, container: Container) =
@@ -1451,7 +1482,7 @@ proc updateReadLine*(pager: Pager) =
           )
         else:
           pager.saveTo(data, lineedit.news)
-      of lmISearchF, lmISearchB: discard
+      of lmISearchF, lmISearchB, lmAlert: discard
     of lesCancel:
       case pager.linemode
       of lmUsername, lmPassword: pager.discardBuffer()
diff --git a/todo b/todo
index c08b289a..e6b14ece 100644
--- a/todo
+++ b/todo
@@ -1,7 +1,6 @@
 compilation:
 - reduce binary size
 	* fbf for unifont
-	* maybe use system wcwidth?
 charsets:
 - set up some fuzzer
 - use appropriate charsets in forms, urls, etc.
@@ -14,14 +13,13 @@ display:
 config:
 - important: fix crash on missing /tmp dir with default config
 - important: config editor
-- completely replace siteconf; the new solution should:
-	* not be based on table arrays
-	* allow overriding pretty much every global value per URL
-	* allow better URL matching (regexes aren't great for this task)
-	* be called url-config
-	* allow matching $TERM string, buffer groups (but maybe this should
-	  be a separate setting?)
-- add per-scheme configuration (e.g. proto.gemini.known-hosts = '/some/path')
+- switch from table arrays to tables
+- better siteconf URL matching
+- $TERM-based display config
+* better path handling (e.g. inline files, so we could get rid of css
+  "include/inline" etc.)
+- add per-scheme env var configuration (e.g.
+  proto.gemini.known-hosts = '/some/path'; maybe also with inline JS?)
 - add RPC for CGI scripts e.g. toggle settings/issue downloads/etc
 	* also some way to set permissions for RPC calls
 buffer:
@@ -51,7 +49,7 @@ javascript:
 - add support for JS mixins
 - distinguish double from unrestricted double
 - better dom support: more events, CSSOM, ...
-- implement ReadableStream, XHR
+- ReadableStream
 - separate console for each buffer
 - buffer selection
 layout engine:
@@ -72,12 +70,13 @@ layout engine:
 - writing-mode, grid, ruby, ... (i.e. cool new stuff)
 images:
 - z order, proper image blending
-- incremental decoding, interlaced images, animation
+- animation
 man:
 - add a DOM -> man page converter so that we do not depend on pandoc
   for man page conversion
+- move default keybinding definitions to man page
 gmifetch:
 - rewrite in Nim
-etc:
-- orc support
-- maybe windows support? (blocker: needs a windows machine)
+tests:
+- network/XHR (make net test async?)
+- pager? (how?)