diff options
Diffstat (limited to 'lisp/eshell/esh-var.el')
-rw-r--r-- | lisp/eshell/esh-var.el | 198 |
1 files changed, 117 insertions, 81 deletions
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el index 1d5d85debad..17add9b6685 100644 --- a/lisp/eshell/esh-var.el +++ b/lisp/eshell/esh-var.el @@ -39,11 +39,6 @@ ;; ;; Only "MYVAR" is part of the variable name in this case. ;; -;; $#VARIABLE -;; -;; Returns the length of the value of VARIABLE. This could also be -;; done using the `length' Lisp function. -;; ;; $(lisp) ;; ;; Returns result of Lisp evaluation. Note: Used alone like this, it @@ -61,38 +56,35 @@ ;; Evaluates an eshell subcommand, redirecting the output to a ;; temporary file, and returning the file name. ;; -;; $ANYVAR[10] +;; $EXPR[10] ;; -;; Return the 10th element of ANYVAR. If ANYVAR's value is a string, -;; it will be split in order to make it a list. The splitting will -;; occur at whitespace. +;; Return the 10th element of $EXPR, which can be any dollar +;; expression. If $EXPR's value is a string, it will be split in +;; order to make it a list. The splitting will occur at whitespace. ;; -;; $ANYVAR[: 10] +;; $EXPR[10 20] ;; -;; As above, except that splitting occurs at the colon now. +;; As above, but instead of returning a single element, it now returns a +;; list of two elements. ;; -;; $ANYVAR[: 10 20] +;; $EXPR[: 10] ;; -;; As above, but instead of returning just a string, it now returns a -;; list of two strings. If the result is being interpolated into a -;; larger string, this list will be flattened into one big string, -;; with each element separated by a space. +;; Like $EXPR[10], except that splitting occurs at the colon now. ;; -;; $ANYVAR["\\\\" 10] +;; $EXPR["\\\\" 10] ;; ;; Separate on backslash characters. Actually, the first argument -- -;; if it doesn't have the form of a number, or a plain variable name -;; -- can be any regular expression. So to split on numbers, use -;; '$ANYVAR["[0-9]+" 10 20]'. +;; if it doesn't have the form of a number -- can be any regular +;; expression. So to split on numbers, use '$EXPR["[0-9]+" 10 20]'. ;; -;; $ANYVAR[hello] +;; $EXPR[hello] ;; -;; Calls `assoc' on ANYVAR with 'hello', expecting it to be an alist. +;; Calls `assoc' on $EXPR with 'hello', expecting it to be an alist. ;; -;; $#ANYVAR[hello] +;; $#EXPR ;; -;; Returns the length of the cdr of the element of ANYVAR who car is -;; equal to "hello". +;; Returns the length of the value of $EXPR. This could also be +;; done using the `length' Lisp function. ;; ;; There are also a few special variables defined by Eshell. '$$' is ;; the value of the last command (t or nil, in the case of an external @@ -157,8 +149,8 @@ if they are quoted with a backslash." (defcustom eshell-variable-aliases-list `(;; for eshell.el - ("COLUMNS" ,(lambda (_indices) (window-width)) t) - ("LINES" ,(lambda (_indices) (window-height)) t) + ("COLUMNS" ,(lambda (_indices) (window-body-width nil 'remap)) t) + ("LINES" ,(lambda (_indices) (window-body-height nil 'remap)) t) ;; for eshell-cmd.el ("_" ,(lambda (indices) @@ -193,7 +185,7 @@ list of the indices that was used in the reference, and whether the user is requesting the length of the ultimate element. For example, a reference of `$NAME[10][20]' would result in the function for alias `NAME' being called (assuming it were aliased to a function), and the -arguments passed to this function would be the list '(10 20)', and +arguments passed to this function would be the list `(10 20)', and nil. If the value is a string, return the value for the variable with that @@ -211,14 +203,11 @@ Additionally, each member may specify if it should be copied to the environment of created subprocesses." :type '(repeat (list string sexp (choice (const :tag "Copy to environment" t) - (const :tag "Use only in Eshell" nil))))) - -(put 'eshell-variable-aliases-list 'risky-local-variable t) + (const :tag "Use only in Eshell" nil)))) + :risky t) -(defvar eshell-var-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c M-v") #'eshell-insert-envvar) - map)) +(defvar-keymap eshell-var-mode-map + "C-c M-v" #'eshell-insert-envvar) ;;; Functions: @@ -413,27 +402,34 @@ process any indices that come after the variable reference." (let* ((get-len (when (eq (char-after) ?#) (forward-char) t)) value indices) - (setq value (eshell-parse-variable-ref) + (setq value (eshell-parse-variable-ref get-len) indices (and (not (eobp)) (eq (char-after) ?\[) (eshell-parse-indices)) ;; This is an expression that will be evaluated by `eshell-do-eval', ;; which only support let-binding of dynamically-scoped vars - value `(let ((indices ',indices)) ,value)) - (if get-len - `(length ,value) - value))) - -(defun eshell-parse-variable-ref () + value `(let ((indices (eshell-eval-indices ',indices))) ,value)) + (when get-len + (setq value `(length ,value))) + (when eshell-current-quoted + (setq value `(eshell-stringify ,value))) + value)) + +(defun eshell-parse-variable-ref (&optional modifier-p) "Eval a variable reference. Returns a Lisp form which, if evaluated, will return the value of the variable. -Possible options are: +If MODIFIER-P is non-nil, the value of the variable will be +modified by some function. If MODIFIER-P is nil, the value will be +used as-is; this allows optimization of some kinds of variable +references. + +Possible variable references are: NAME an environment or Lisp variable value \"LONG-NAME\" disambiguates the length of the name - 'LONG-NAME' as above + `LONG-NAME' as above {COMMAND} result of command is variable's value (LISP-FORM) result of Lisp form is variable's value <COMMAND> write the output of command to a temporary file; @@ -443,18 +439,26 @@ Possible options are: (let ((end (eshell-find-delimiter ?\{ ?\}))) (if (not end) (throw 'eshell-incomplete ?\{) + (forward-char) (prog1 - `(eshell-convert - (eshell-command-to-value - (eshell-as-subcommand - ,(eshell-parse-command (cons (1+ (point)) end))))) + `(eshell-apply-indices + (eshell-convert + (eshell-command-to-value + (eshell-as-subcommand + ,(let ((subcmd (or (eshell-unescape-inner-double-quote end) + (cons (point) end))) + (eshell-current-quoted nil)) + (eshell-parse-command subcmd)))) + ;; If this is a simple double-quoted form like + ;; "${COMMAND}" (i.e. no indices after the subcommand + ;; and no `#' modifier before), ensure we convert to a + ;; single string. This avoids unnecessary work + ;; (e.g. splitting the output by lines) when it would + ;; just be joined back together afterwards. + ,(when (and (not modifier-p) eshell-current-quoted) + '(not indices))) + indices ,eshell-current-quoted) (goto-char (1+ end)))))) - ((memq (char-after) '(?\' ?\")) - (let ((name (if (eq (char-after) ?\') - (eshell-parse-literal-quote) - (eshell-parse-double-quote)))) - (if name - `(eshell-get-variable ,(eval name) indices)))) ((eq (char-after) ?\<) (let ((end (eshell-find-delimiter ?\< ?\>))) (if (not end) @@ -466,7 +470,9 @@ Possible options are: `(let ((eshell-current-handles (eshell-create-handles ,temp 'overwrite))) (progn - (eshell-as-subcommand ,(eshell-parse-command cmd)) + (eshell-as-subcommand + ,(let ((eshell-current-quoted nil)) + (eshell-parse-command cmd))) (ignore (nconc eshell-this-command-hook ;; Quote this lambda; it will be evaluated @@ -475,22 +481,36 @@ Possible options are: ;; properly. See bug#54190. (list (function (lambda () (delete-file ,temp)))))) - (quote ,temp))) + (eshell-apply-indices ,temp indices ,eshell-current-quoted))) (goto-char (1+ end))))))) ((eq (char-after) ?\() (condition-case nil - `(eshell-command-to-value - (eshell-lisp-command - ',(read (current-buffer)))) + `(eshell-apply-indices + (eshell-command-to-value + (eshell-lisp-command + ',(read (or (eshell-unescape-inner-double-quote (point-max)) + (current-buffer))))) + indices ,eshell-current-quoted) (end-of-file (throw 'eshell-incomplete ?\()))) + ((looking-at (rx-to-string + `(or "'" ,(if eshell-current-quoted "\\\"" "\"")))) + (eshell-with-temp-command + (or (eshell-unescape-inner-double-quote (point-max)) + (cons (point) (point-max))) + (let ((name (if (eq (char-after) ?\') + (eshell-parse-literal-quote) + (eshell-parse-double-quote)))) + (when name + `(eshell-get-variable ,(eval name) indices ,eshell-current-quoted))))) ((assoc (char-to-string (char-after)) eshell-variable-aliases-list) (forward-char) - `(eshell-get-variable ,(char-to-string (char-before)) indices)) + `(eshell-get-variable ,(char-to-string (char-before)) indices + ,eshell-current-quoted)) ((looking-at eshell-variable-name-regexp) (prog1 - `(eshell-get-variable ,(match-string 0) indices) + `(eshell-get-variable ,(match-string 0) indices ,eshell-current-quoted) (goto-char (match-end 0)))) (t (error "Invalid variable reference")))) @@ -498,21 +518,33 @@ Possible options are: (defvar eshell-glob-function) (defun eshell-parse-indices () - "Parse and return a list of list of indices." + "Parse and return a list of index-lists. + +For example, \"[0 1][2]\" becomes: + ((\"0\" \"1\") (\"2\")." (let (indices) (while (eq (char-after) ?\[) (let ((end (eshell-find-delimiter ?\[ ?\]))) (if (not end) (throw 'eshell-incomplete ?\[) (forward-char) - (let (eshell-glob-function) - (setq indices (cons (eshell-parse-arguments (point) end) - indices))) + (eshell-with-temp-command (or (eshell-unescape-inner-double-quote end) + (cons (point) end)) + (let (eshell-glob-function (eshell-current-quoted nil)) + (setq indices (cons (eshell-parse-arguments + (point-min) (point-max)) + indices)))) (goto-char (1+ end))))) (nreverse indices))) -(defun eshell-get-variable (name &optional indices) - "Get the value for the variable NAME." +(defun eshell-eval-indices (indices) + "Evaluate INDICES, a list of index-lists generated by `eshell-parse-indices'." + (mapcar (lambda (i) (mapcar #'eval i)) indices)) + +(defun eshell-get-variable (name &optional indices quoted) + "Get the value for the variable NAME. +INDICES is a list of index-lists (see `eshell-parse-indices'). +If QUOTED is non-nil, this was invoked inside double-quotes." (let* ((alias (assoc name eshell-variable-aliases-list)) (var (if alias (cadr alias) @@ -533,9 +565,9 @@ Possible options are: (symbol-value var)) (t (error "Unknown variable `%s'" (eshell-stringify var)))) - indices)))) + indices quoted)))) -(defun eshell-apply-indices (value indices) +(defun eshell-apply-indices (value indices &optional quoted) "Apply to VALUE all of the given INDICES, returning the sub-result. The format of INDICES is: @@ -544,12 +576,18 @@ The format of INDICES is: Each member of INDICES represents a level of nesting. If the first member of a sublist is not an integer or name, and the value it's -reference is a string, that will be used as the regexp with which is -to divide the string into sub-parts. The default is whitespace. +referencing is a string, that will be used as the regexp with which +is to divide the string into sub-parts. The default is whitespace. Otherwise, each INT-OR-NAME refers to an element of the list value. Integers imply a direct index, and names, an associate lookup using `assoc'. +If QUOTED is non-nil, this was invoked inside double-quotes. +This affects the behavior of splitting strings: without quoting, +the split values are converted to numbers via +`eshell-convert-to-number' if possible; with quoting, they're +left as strings. + For example, to retrieve the second element of a user's record in '/etc/passwd', the variable reference would look like: @@ -557,16 +595,14 @@ For example, to retrieve the second element of a user's record in (while indices (let ((refs (car indices))) (when (stringp value) - (let (separator) - (if (not (or (not (stringp (caar indices))) - (string-match - (concat "^" eshell-variable-name-regexp "$") - (caar indices)))) - (setq separator (caar indices) - refs (cdr refs))) - (setq value - (mapcar #'eshell-convert - (split-string value separator))))) + (let (separator (index (caar indices))) + (when (and (stringp index) + (not (get-text-property 0 'number index))) + (setq separator index + refs (cdr refs))) + (setq value (split-string value separator)) + (unless quoted + (setq value (mapcar #'eshell-convert-to-number value))))) (cond ((< (length refs) 0) (error "Invalid array variable index: %s" |