mirror of
https://gitlab.com/vindarel/ciel.git
synced 2025-12-05 18:20:34 -08:00
REPL, visual commands: allow "sudo" and "ENV" modifiers
a command like: ENV=env sudo htop will be recognized accordingly as visual. Thanks @ambrevar https://github.com/ruricolist/cmd/issues/10
This commit is contained in:
parent
db259c7f56
commit
8d2bccab0b
5 changed files with 153 additions and 53 deletions
2
ciel.asd
2
ciel.asd
|
|
@ -85,6 +85,7 @@
|
|||
:cl-punch
|
||||
|
||||
:serapeum
|
||||
:shlex
|
||||
|
||||
;; tests
|
||||
:fiveam
|
||||
|
|
@ -111,6 +112,7 @@
|
|||
((:file "ciel")
|
||||
))
|
||||
(:file "repl")
|
||||
(:file "shell-utils")
|
||||
(:file "repl-utils"))
|
||||
|
||||
:build-operation "program-op"
|
||||
|
|
|
|||
24
docs/repl.md
24
docs/repl.md
|
|
@ -63,19 +63,31 @@ Use square brackets `[...]` to write a shell script, and use `$` inside it to es
|
|||
|
||||
The result is concatenated into a string and printed on stdout.
|
||||
|
||||
This feature is only available in CIEL's REPL, not on the CIEL-USER package.
|
||||
This feature is only available by default in CIEL's REPL, not on the
|
||||
CIEL-USER package. To enable it yourself, do:
|
||||
|
||||
Some programs are **visual** / interactive / ncurses-based, and need
|
||||
(ciel:enable-shell-passthrough)
|
||||
|
||||
But, some programs are **visual**, or interactive, because they have an ncurses or similar interface. They need
|
||||
to be run in their own terminal window. CIEL recognizes a few (`vim`,
|
||||
`htop`, `man`…) and runs them in the first terminal emulator found on
|
||||
the system of `terminator`, `xterm`, `gnome-terminal`). See the
|
||||
`*visual-commands*` variable.
|
||||
`htop`, `man`… see `*visual-commands*`) and runs them in the first terminal emulator found on
|
||||
the system: `terminator`, `xterm`, `gnome-terminal`, Emacs' `vterm` (with emacsclient) or your own.
|
||||
|
||||
So, you can run a command similar to this one:
|
||||
|
||||
ENV=env sudo htop
|
||||
|
||||
and it will open in a new terminal (hint: a visual command doesn't require the `!` prefix).
|
||||
|
||||
To use your terminal emulator of choice, do:
|
||||
|
||||
(push "myterminal" *visual-terminal-emulator-choices*)
|
||||
|
||||
> Note: this feature is experimental.
|
||||
|
||||
> Note: we encourage our users to use Emacs rather than a terminal!
|
||||
|
||||
We use the [Clesh](https://github.com/Neronus/clesh) library.
|
||||
We use the [Clesh](https://github.com/Neronus/clesh) library for the `!` shell passthrough.
|
||||
|
||||
See also [SHCL](https://github.com/bradleyjensen/shcl) for a more unholy union of posix-shell and Common Lisp.
|
||||
|
||||
|
|
|
|||
|
|
@ -47,49 +47,3 @@ bar:qux
|
|||
(format t "~c[1C" #\esc))
|
||||
(finish-output)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; Run visual / interactive / ncurses commands in their terminal window.
|
||||
;;;
|
||||
;;; How to guess a program is interactive?
|
||||
;;; We currently look from a hand-made list (à la Eshell).
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defparameter *visual-commands*
|
||||
'(;; "emacs -nw" ;; in eshell, concept of visual-subcommands.
|
||||
"vim" "vi"
|
||||
"nano"
|
||||
"htop" "top"
|
||||
"man" "less" "more"
|
||||
"screen" "tmux"
|
||||
"lynx" "links" "mutt" "pine" "tin" "elm" "ncftp" "ncdu"
|
||||
"ranger"
|
||||
;; last but not least
|
||||
"ciel-repl")
|
||||
"List of visual/interactive/ncurses-based programs that will be run in their own terminal window.")
|
||||
|
||||
(defparameter *visual-terminal-emulator-choices*
|
||||
'("terminator" "x-terminal-emulator" "xterm" "gnome-terminal"))
|
||||
|
||||
(defparameter *visual-terminal-switches* '("-e")
|
||||
"Default options to the terminal. `-e' aka `--command'.")
|
||||
|
||||
(defun find-terminal ()
|
||||
"Return the first terminal emulator found on the system from the `*visual-terminal-emulator-choices*' list."
|
||||
(loop for program in *visual-terminal-emulator-choices*
|
||||
when (which:which program)
|
||||
return program))
|
||||
|
||||
(defun visual-command-p (text)
|
||||
"The command TEXT starts by a known visual command, listed in `*visual-commands*'."
|
||||
(let* ((cmd (string-left-trim "!" text)) ;; strip clesh syntax.
|
||||
(first-word (first (str:words cmd))))
|
||||
;; This will be smarter. https://github.com/ruricolist/cmd/issues/10
|
||||
(find first-word *visual-commands* :test #'equalp)))
|
||||
|
||||
(defun run-visual-command (text)
|
||||
"Run this text command into another terminal window."
|
||||
(let ((cmd (string-left-trim "!" text)))
|
||||
(uiop:launch-program `( ,(find-terminal)
|
||||
;; quick way to flatten the list of switches:
|
||||
,@*visual-terminal-switches*
|
||||
,cmd))))
|
||||
|
|
|
|||
126
shell-utils.lisp
Normal file
126
shell-utils.lisp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
(in-package :sbcli)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; Run visual / interactive / ncurses commands in their terminal window.
|
||||
;;;
|
||||
;;; How to guess a program is interactive?
|
||||
;;; We currently look from a hand-made list (à la Eshell).
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; thanks @ambrevar: https://github.com/ruricolist/cmd/issues/10
|
||||
;; for handling command wrappers (sudo) and vterm.
|
||||
|
||||
(defparameter *visual-commands*
|
||||
'(;; "emacs -nw" ;; in eshell, see concept of visual-subcommands.
|
||||
"vim" "vi"
|
||||
"nano"
|
||||
"htop" "top"
|
||||
"man" "less" "more"
|
||||
"screen" "tmux"
|
||||
"lynx" "links" "mutt" "pine" "tin" "elm" "ncftp" "ncdu"
|
||||
"ranger"
|
||||
;; last but not least
|
||||
"ciel-repl")
|
||||
"List of visual/interactive/ncurses-based programs that will be run in their own terminal window.")
|
||||
|
||||
(defun vterm-terminal (cmd)
|
||||
"Build a command (string) to send to emacsclient to open CMD with Emacs' vterm."
|
||||
(list
|
||||
"emacsclient" "--eval"
|
||||
(let ((*print-case* :downcase))
|
||||
(write-to-string
|
||||
`(progn
|
||||
(vterm)
|
||||
(vterm-insert ,cmd)
|
||||
(vterm-send-return))))))
|
||||
|
||||
(defparameter *visual-terminal-emulator-choices*
|
||||
'("terminator" "x-terminal-emulator" "xterm" "gnome-terminal"
|
||||
#'vterm-terminal)
|
||||
"List of terminals, either a string or a function (that returns a more complete command, as a string).")
|
||||
|
||||
(defparameter *visual-terminal-switches* '("-e")
|
||||
"Default options to the terminal. `-e' aka `--command'.")
|
||||
|
||||
(defvar *command-wrappers* '("sudo" "env"))
|
||||
|
||||
(defun find-terminal ()
|
||||
"Return the first terminal emulator found on the system from the `*visual-terminal-emulator-choices*' list."
|
||||
(loop for program in *visual-terminal-emulator-choices*
|
||||
if (and (stringp program)
|
||||
(which:which program))
|
||||
return program
|
||||
else if (functionp program) return program))
|
||||
|
||||
(defun basename (arg)
|
||||
(when arg
|
||||
(namestring (pathname-name arg))))
|
||||
|
||||
(defun shell-command-wrapper-p (command)
|
||||
(find (basename command)
|
||||
*command-wrappers*
|
||||
:test #'string-equal))
|
||||
|
||||
(defun shell-flag-p (arg)
|
||||
(str:starts-with-p "-" arg))
|
||||
|
||||
(defun shell-variable-p (arg)
|
||||
(and (< 1 (length arg))
|
||||
(str:contains? "=" (subseq arg 1))))
|
||||
|
||||
(defun shell-first-positional-argument (command)
|
||||
"Recursively find the first command that's not a flag, not a variable setting and
|
||||
not in `*command-wrappers*'."
|
||||
(when command
|
||||
(if (or (shell-flag-p (first command))
|
||||
(shell-variable-p (first command))
|
||||
(shell-command-wrapper-p (first command)))
|
||||
(shell-first-positional-argument (rest command))
|
||||
(first command))))
|
||||
|
||||
(defun shell-ensure-clean-command-list (command)
|
||||
"Return a list of commands, stripped out of a potential \"!\" prefix from Clesh syntax."
|
||||
(unless (consp command)
|
||||
(setf command (shlex:split command)))
|
||||
;; remove optional ! clesh syntax.
|
||||
(setf (first command)
|
||||
(string-left-trim "!" (first command)))
|
||||
;; remove blank strings, in case we wrote "! command".
|
||||
(remove-if #'str:blankp command))
|
||||
|
||||
(defun visual-command-p (command)
|
||||
"Return true if COMMAND runs one of the programs in `*visual-commands*'.
|
||||
COMMAND is either a list of strings or a string.
|
||||
`*command-wrappers*' are supported, i.e. the following works:
|
||||
|
||||
env FOO=BAR sudo -i powertop"
|
||||
(setf command (shell-ensure-clean-command-list command))
|
||||
(let ((cmd (shell-first-positional-argument command)))
|
||||
(when cmd
|
||||
(find (basename cmd)
|
||||
*visual-commands*
|
||||
:test #'string=))))
|
||||
|
||||
(defun run-visual-command (text)
|
||||
"Run this command (string) in another terminal window."
|
||||
(let* ((cmd (string-left-trim "!" text))
|
||||
(terminal (find-terminal)))
|
||||
(if terminal
|
||||
(cond
|
||||
((stringp terminal)
|
||||
(uiop:launch-program `(,terminal
|
||||
;; flatten the list of switches
|
||||
,@*visual-terminal-switches*
|
||||
,cmd)))
|
||||
((functionp terminal)
|
||||
(uiop:launch-program (funcall terminal cmd)))
|
||||
(t
|
||||
(format *error-output* "We cannot use a terminal designator of type ~a. Please use a string (\"xterm\") or a function that returns a string." (type-of terminal))))
|
||||
;; else no terminal found.
|
||||
(format *error-output* "Could not find a terminal emulator amongst the list ~a: ~s"
|
||||
'*visual-terminal-emulator-choices*
|
||||
*visual-terminal-emulator-choices*))))
|
||||
|
||||
#+(or)
|
||||
(assert (string-equal "htop"
|
||||
(visual-command-p "env rst=ldv sudo htop")))
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
(in-package :cl-user)
|
||||
(defpackage ciel
|
||||
(:use :cl))
|
||||
(:use :cl)
|
||||
(:export :enable-shell-passthrough))
|
||||
|
||||
(in-package :ciel)
|
||||
|
||||
|
|
@ -332,3 +334,7 @@ We currently only try this with serapeum. See *deps/serapeum/sequences-hashtable
|
|||
|
||||
(when *pretty-print-hash-tables*
|
||||
(toggle-pretty-print-hash-table t))
|
||||
|
||||
(defun enable-shell-passthrough ()
|
||||
"Enable the shell passthrough with \"!\". Enable Clesh's readtable."
|
||||
(named-readtables:in-readtable clesh:syntax))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue