about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-09-09 01:44:58 +0200
committerbptato <nincsnevem662@gmail.com>2023-09-09 01:52:24 +0200
commit11219c7d21c8ec7f438d52de063c4ac25f84711d (patch)
tree36b1308d747cc467dc7dad2713eb2cc546390026
parent6c435aa6c186c2fe7ec72cee2a5ae2f1ae8b22a7 (diff)
downloadchawan-11219c7d21c8ec7f438d52de063c4ac25f84711d.tar.gz
add extern, refactor some term functions
* Add an extern() call. Maybe it should be defined on client. It
  certainly should accept a dictionary instead of the enum type we use
  now. Perhaps it should return the error code?
  I'll leave it undocumented until I figure this out.
* Refactor enableRawMode, unblockStdin, etc. so that they operate on
  the term object instead of global state.
* Move editor to a separate folder, and factor out runprocess into
  a different module.
-rw-r--r--res/config.toml5
-rw-r--r--src/buffer/container.nim5
-rw-r--r--src/display/client.nim8
-rw-r--r--src/display/pager.nim30
-rw-r--r--src/display/term.nim101
-rw-r--r--src/extern/editor.nim (renamed from src/ips/editor.nim)20
-rw-r--r--src/extern/runproc.nim29
7 files changed, 127 insertions, 71 deletions
diff --git a/res/config.toml b/res/config.toml
index 14b746fd..d84f9b69 100644
--- a/res/config.toml
+++ b/res/config.toml
@@ -131,6 +131,11 @@ C-w = '''
 config.search.wrap = !config.search.wrap;
 pager.alert("Wrap search " + (config.search.wrap ? "on" : "off"));
 '''
+M-y = '''
+pager.extern('printf \'%s\\n\' "$CHA_URL" | xsel -bi', "no-suspend-setenv") ?
+	pager.alert("Copied URL to clipboard.") :
+	pager.alert("Failed to copy URL to clipboard. (Is xsel installed?)");
+'''
 
 [line]
 C-m = 'line.submit()'
diff --git a/src/buffer/container.nim b/src/buffer/container.nim
index 2a000bbf..edba490f 100644
--- a/src/buffer/container.nim
+++ b/src/buffer/container.nim
@@ -24,6 +24,8 @@ import types/url
 import utils/mimeguess
 import utils/twtstr
 
+import chakasu/charset
+
 type
   CursorPosition* = object
     cursorx*: int
@@ -124,6 +126,9 @@ proc newBuffer*(forkserver: ForkServer, mainproc: Pid, config: BufferConfig,
     )
   )
 
+func charset*(container: Container): Charset =
+  return container.source.charset
+
 func contentType*(container: Container): Option[string] {.jsfget.} =
   return container.source.contenttype
 
diff --git a/src/display/client.nim b/src/display/client.nim
index 72a42124..c8a4ab1f 100644
--- a/src/display/client.nim
+++ b/src/display/client.nim
@@ -132,16 +132,14 @@ proc runJSJobs(client: Client) =
   client.jsrt.runJSJobs(client.console.err)
 
 proc evalJS(client: Client, src, filename: string, module = false): JSValue =
-  if client.console.tty != nil:
-    unblockStdin(client.console.tty.getFileHandle())
+  client.pager.term.unblockStdin()
   let flags = if module:
     JS_EVAL_TYPE_MODULE
   else:
     JS_EVAL_TYPE_GLOBAL
   result = client.jsctx.eval(src, filename, flags)
   client.runJSJobs()
-  if client.console.tty != nil:
-    restoreStdin(client.console.tty.getFileHandle())
+  client.pager.term.restoreStdin()
 
 proc evalJSFree(client: Client, src, filename: string) =
   JS_FreeValue(client.jsctx, client.evalJS(src, filename))
@@ -231,7 +229,7 @@ proc handleCommandInput(client: Client, c: char) =
     client.pager.refreshStatusMsg()
 
 proc input(client: Client) =
-  restoreStdin(client.console.tty.getFileHandle())
+  client.pager.term.restoreStdin()
   while true:
     let c = client.console.readChar()
     if client.pager.askpromise != nil:
diff --git a/src/display/pager.nim b/src/display/pager.nim
index 9196b4f9..0229c706 100644
--- a/src/display/pager.nim
+++ b/src/display/pager.nim
@@ -17,6 +17,8 @@ import config/config
 import config/mailcap
 import config/mimetypes
 import display/term
+import extern/editor
+import extern/runproc
 import io/connecterror
 import io/lineedit
 import io/loader
@@ -24,7 +26,6 @@ import io/promise
 import io/request
 import io/tempfile
 import io/window
-import ips/editor
 import ips/forkserver
 import ips/socketstream
 import js/javascript
@@ -831,6 +832,33 @@ proc reload(pager: Pager) {.jsfunc.} =
   pager.gotoURL(newRequest(pager.container.source.location), none(URL),
     pager.container.contenttype, replace = pager.container)
 
+proc setEnvVars(pager: Pager) {.jsfunc.} =
+  try:
+    putEnv("CHA_URL", $pager.container.location)
+    putEnv("CHA_CHARSET", $pager.container.charset)
+  except OSError:
+    pager.alert("Warning: failed to set some environment variables")
+
+type ExternType = enum
+  SUSPEND_SETENV = "suspend-setenv"
+  SUSPEND_SETENV_WAIT = "suspend-setenv-wait"
+  SUSPEND_NO_SETENV = "suspend-no-setenv"
+  SUSPEND_NO_SETENV_WAIT = "suspend-no-setenv-wait"
+  NO_SUSPEND_SETENV = "no-suspend-setenv"
+  NO_SUSPEND_NO_SETENV = "no-suspend-no-setenv"
+
+#TODO this could be handled much better.
+# * suspend, setenv, wait as dict flags
+# * retval as int?
+proc extern(pager: Pager, cmd: string, t = SUSPEND_SETENV): bool {.jsfunc.} =
+  if t in {SUSPEND_SETENV, SUSPEND_SETENV_WAIT, NO_SUSPEND_SETENV}:
+    pager.setEnvVars()
+  if t in {NO_SUSPEND_SETENV, NO_SUSPEND_NO_SETENV}:
+    return runProcess(cmd)
+  else:
+    return runProcess(pager.term, cmd,
+      t in {SUSPEND_SETENV_WAIT, SUSPEND_NO_SETENV_WAIT})
+
 proc authorize(pager: Pager) =
   pager.setLineEdit("Username: ", USERNAME)
 
diff --git a/src/display/term.nim b/src/display/term.nim
index c9981f42..233c9cd4 100644
--- a/src/display/term.nim
+++ b/src/display/term.nim
@@ -1,8 +1,10 @@
 import options
 import os
+import posix
 import streams
 import tables
 import terminal
+import termios
 import unicode
 
 import bindings/termcap
@@ -58,6 +60,10 @@ type
     tc: Termcap
     tname: string
     set_title: bool
+    stdin_unblocked: bool
+    orig_flags: cint
+    orig_flags2: cint
+    orig_termios: Termios
 
 func hascap(term: Terminal, c: TermcapCap): bool = term.tc.caps[c] != nil
 func cap(term: Terminal, c: TermcapCap): string = $term.tc.caps[c]
@@ -138,6 +144,11 @@ proc clearDisplay(term: Terminal): string =
 proc isatty(term: Terminal): bool =
   term.infile != nil and term.infile.isatty() and term.outfile.isatty()
 
+proc anyKey*(term: Terminal) =
+  if term.isatty():
+    term.outfile.write("[Hit any key]")
+    discard term.infile.readChar()
+
 proc resetFormat(term: Terminal): string =
   when termcap_found:
     if term.isatty():
@@ -556,60 +567,51 @@ proc outputGrid*(term: Terminal) =
 proc clearCanvas*(term: Terminal) =
   term.cleared = false
 
-when defined(posix):
-  import posix
-  import termios
-
-  # see https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
-  var orig_termios: Termios
-  var stdin_fileno: FileHandle
-  proc disableRawMode() {.noconv.} =
-    discard tcSetAttr(stdin_fileno, TCSAFLUSH, addr orig_termios)
-
-  proc enableRawMode(fileno: FileHandle) =
-    stdin_fileno = fileno
-    discard tcGetAttr(fileno, addr orig_termios)
-    var raw = orig_termios
-    raw.c_iflag = raw.c_iflag and not (BRKINT or ICRNL or INPCK or ISTRIP or IXON)
-    raw.c_oflag = raw.c_oflag and not (OPOST)
-    raw.c_cflag = raw.c_cflag or CS8
-    raw.c_lflag = raw.c_lflag and not (ECHO or ICANON or ISIG or IEXTEN)
-    discard tcSetAttr(fileno, TCSAFLUSH, addr raw)
-
-  var orig_flags: cint
-  var stdin_unblocked = false
-  proc unblockStdin*(fileno: FileHandle) =
-    orig_flags = fcntl(fileno, F_GETFL, 0)
-    let flags = orig_flags or O_NONBLOCK
-    discard fcntl(fileno, F_SETFL, flags)
-    stdin_unblocked = true
-
-  proc restoreStdin*(fileno: FileHandle) =
-    if stdin_unblocked:
-      discard fcntl(fileno, F_SETFL, orig_flags)
-      stdin_unblocked = false
-else:
-  proc disableRawMode() =
-    discard
-
-  proc enableRawMode(fileno: FileHandle) =
-    discard
-
-  proc unblockStdin*(): cint =
-    discard
-
-  proc restoreStdin*(flags: cint) =
-    discard
+# see https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
+proc disableRawMode(term: Terminal) =
+  let fd = term.infile.getFileHandle()
+  discard tcSetAttr(fd, TCSAFLUSH, addr term.orig_termios)
+
+proc enableRawMode(term: Terminal) =
+  let fd = term.infile.getFileHandle()
+  discard tcGetAttr(fd, addr term.orig_termios)
+  var raw = term.orig_termios
+  raw.c_iflag = raw.c_iflag and not (BRKINT or ICRNL or INPCK or ISTRIP or IXON)
+  raw.c_oflag = raw.c_oflag and not (OPOST)
+  raw.c_cflag = raw.c_cflag or CS8
+  raw.c_lflag = raw.c_lflag and not (ECHO or ICANON or ISIG or IEXTEN)
+  discard tcSetAttr(fd, TCSAFLUSH, addr raw)
+
+proc unblockStdin*(term: Terminal) =
+  if term.isatty():
+    let fd = term.infile.getFileHandle()
+    term.orig_flags = fcntl(fd, F_GETFL, 0)
+    let flags = term.orig_flags or O_NONBLOCK
+    discard fcntl(fd, F_SETFL, flags)
+    term.stdin_unblocked = true
+
+proc restoreStdin*(term: Terminal) =
+  if term.stdin_unblocked:
+    let fd = term.infile.getFileHandle()
+    discard fcntl(fd, F_SETFL, term.orig_flags)
+    term.stdin_unblocked = false
 
 proc quit*(term: Terminal) =
   if term.isatty():
-    disableRawMode()
+    term.disableRawMode()
     if term.smcup:
       term.write(term.disableAltScreen())
     else:
       term.write(term.cursorGoto(0, term.attrs.height - 1))
     term.showCursor()
     term.cleared = false
+    if term.stdin_unblocked:
+      let fd = term.infile.getFileHandle()
+      term.orig_flags2 = fcntl(fd, F_GETFL, 0)
+      discard fcntl(fd, F_SETFL, term.orig_flags2 and (not O_NONBLOCK))
+      term.stdin_unblocked = false
+    else:
+      term.orig_flags2 = -1
   term.flush()
 
 when termcap_found:
@@ -651,14 +653,19 @@ proc detectTermAttributes(term: Terminal) =
 proc start*(term: Terminal, infile: File) =
   term.infile = infile
   if term.isatty():
-    enableRawMode(infile.getFileHandle())
+    term.enableRawMode()
   term.detectTermAttributes()
   if term.smcup:
     term.write(term.enableAltScreen())
 
 proc restart*(term: Terminal) =
   if term.isatty():
-    enableRawMode(term.infile.getFileHandle())
+    term.enableRawMode()
+    if term.orig_flags2 != -1:
+      let fd = term.infile.getFileHandle()
+      discard fcntl(fd, F_SETFL, term.orig_flags2)
+      term.orig_flags2 = 0
+      term.stdin_unblocked = true
   if term.smcup:
     term.write(term.enableAltScreen())
 
diff --git a/src/ips/editor.nim b/src/extern/editor.nim
index 19ba965a..58f3d199 100644
--- a/src/ips/editor.nim
+++ b/src/extern/editor.nim
@@ -1,8 +1,8 @@
 import os
-import posix
 
 import config/config
 import display/term
+import extern/runproc
 import io/tempfile
 
 func formatEditorName(editor, file: string, line: int): string =
@@ -31,9 +31,6 @@ func formatEditorName(editor, file: string, line: int): string =
       result &= ' '
     result &= file
 
-proc c_system(cmd: cstring): cint {.
-  importc: "system", header: "<stdlib.h>".}
-
 proc openEditor*(term: Terminal, config: Config, file: string, line = 1): bool =
   var editor = config.external.editor
   if editor == "":
@@ -41,20 +38,7 @@ proc openEditor*(term: Terminal, config: Config, file: string, line = 1): bool =
     if editor == "":
       editor = "vi %s +%d"
   let cmd = formatEditorName(editor, file, line)
-  term.quit()
-  let wstatus = c_system(cstring(cmd))
-  if wstatus == -1:
-    result = false
-  else:
-    result = WIFEXITED(wstatus) and WEXITSTATUS(wstatus) == 0
-    if not result:
-      # Hack.
-      #TODO this is a very bad idea, e.g. say the editor is writing into the
-      # file, then receives SIGINT, now the file is corrupted but Chawan will
-      # happily read it as if nothing happened.
-      # We should find a proper solution for this.
-      result = WIFSIGNALED(wstatus) and WTERMSIG(wstatus) == SIGINT
-  term.restart()
+  return runProcess(term, cmd)
 
 proc openInEditor*(term: Terminal, config: Config, input: var string): bool =
   try:
diff --git a/src/extern/runproc.nim b/src/extern/runproc.nim
new file mode 100644
index 00000000..c49d98db
--- /dev/null
+++ b/src/extern/runproc.nim
@@ -0,0 +1,29 @@
+import posix
+
+import display/term
+
+proc c_system(cmd: cstring): cint {.
+  importc: "system", header: "<stdlib.h>".}
+
+# Run process (without suspending the terminal controller).
+proc runProcess*(cmd: string): bool =
+  let wstatus = c_system(cstring(cmd))
+  if wstatus == -1:
+    result = false
+  else:
+    result = WIFEXITED(wstatus) and WEXITSTATUS(wstatus) == 0
+    if not result:
+      # Hack.
+      #TODO this is a very bad idea, e.g. say the editor is writing into the
+      # file, then receives SIGINT, now the file is corrupted but Chawan will
+      # happily read it as if nothing happened.
+      # We should find a proper solution for this.
+      result = WIFSIGNALED(wstatus) and WTERMSIG(wstatus) == SIGINT
+
+# Run process (and suspend the terminal controller).
+proc runProcess*(term: Terminal, cmd: string, wait = false): bool =
+  term.quit()
+  result = runProcess(cmd)
+  if wait:
+    term.anyKey()
+  term.restart()