@@ -1003,6 +1003,23 @@ be interpreted later as a symlink."
1003
1003
link-name (file-symlink-p link-name) link-target))))))
1004
1004
1005
1005
;;;;; External processes
1006
+
1007
+ (defcustom straight-display-subprocess-prompts nil
1008
+ "Non-nil means display prompts received from subprocess invocations.
1009
+ For example, if a Git clone requires HTTPS or SSH passphrase to be
1010
+ entered, you will be prompted in the minibuffer, and your response will
1011
+ be passed back to Git. Nil means stdin will not be connected to
1012
+ subprocesses, so for example Git will fall back to non-tty
1013
+ authentication if possible, or fail otherwise.
1014
+
1015
+ The default value is nil for now because the code for handling
1016
+ interactive subprocesses is much more complex, and might have unintended
1017
+ side effects, so it is currently being tested for robustness. Please
1018
+ report any bugs you find: performance regressions, execution hangs, and
1019
+ out-of-order command execution are the most likely unintended side
1020
+ effects of enabling this option."
1021
+ :type 'boolean)
1022
+
1006
1023
(defvar straight--process-log t
1007
1024
"If non-nil, log process output to `straight-process-buffer'.")
1008
1025
@@ -1020,6 +1037,62 @@ against the wrong repositories.
1020
1037
If you set this globally to something other than nil, you may be
1021
1038
eaten by a grue.")
1022
1039
1040
+ (defvar straight--process-output-counter 0
1041
+ "Variable incremented each time output is received from a process.
1042
+ This is a way of seeing whether output was received from a process
1043
+ during a call to `accept-process-output'.")
1044
+
1045
+ (defun straight--process-filter (proc string)
1046
+ "Process filter for interactive processes spawned by straight.el.
1047
+ Outputs to the process buffer, but directs username and passphrase
1048
+ prompts to the minibuffer, like Magit. See [1] in comment below for
1049
+ information on PROC and STRING."
1050
+ (cl-incf straight--process-output-counter)
1051
+ ;; [1]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Filter-Functions.html
1052
+ (cl-block nil
1053
+ (condition-case _
1054
+ (when-let ((buf (process-buffer proc)))
1055
+ (with-current-buffer buf
1056
+ (save-excursion
1057
+ (goto-char (point-max))
1058
+ (let ((case-fold-search t)
1059
+ (inhibit-read-only t)
1060
+ (stdin (plist-get (process-plist proc) :stdin)))
1061
+ ;; Here is the part where we try to emulate a very
1062
+ ;; small part of the behavior of a real terminal
1063
+ ;; emulator, and handle carriage returns correctly.
1064
+ ;; (In other words, don't spew a bunch of garbage
1065
+ ;; during a 'git clone' because of the progress bar
1066
+ ;; messages.)
1067
+ (let ((parts (split-string string "\r")))
1068
+ (insert (car parts))
1069
+ (dolist (part (cdr parts))
1070
+ (delete-region
1071
+ (line-beginning-position) (line-end-position))
1072
+ (insert part)))
1073
+ (let ((prompt (thing-at-point 'line)))
1074
+ (when (string-match "username.*: $" prompt)
1075
+ (let ((username (read-string (thing-at-point 'line))))
1076
+ (insert username "\n")
1077
+ (ignore-errors
1078
+ (process-send-string stdin (concat username "\n")))
1079
+ (cl-return))))
1080
+ (let ((prompt (thing-at-point 'line)))
1081
+ (when (string-match
1082
+ "\\(password\\|passphrase\\).*: $" prompt)
1083
+ (let ((password (read-passwd prompt)))
1084
+ (insert (make-string
1085
+ (length password)
1086
+ (or (bound-and-true-p read-hide-char) ?*)))
1087
+ (ignore-errors
1088
+ (process-send-string stdin (concat password "\n")))
1089
+ (clear-string password)
1090
+ (cl-return))))))))
1091
+ (quit
1092
+ (ignore-errors
1093
+ (set-process-filter proc nil)
1094
+ (interrupt-process proc))))))
1095
+
1023
1096
(defconst straight--process-stderr
1024
1097
(expand-file-name (format "straight-stderr-%s" (emacs-pid))
1025
1098
temporary-file-directory)
@@ -1049,26 +1122,75 @@ batch mode."
1049
1122
(unless (derived-mode-p 'special-mode) (special-mode))
1050
1123
(current-buffer)))
1051
1124
1125
+ (defun straight--process-call-interactively (program &rest args)
1126
+ "Run PROGRAM synchronously with ARGS.
1127
+ Return a list of form: (EXITCODE STDOUT STDERR)."
1128
+ (let* ((stdout (get-buffer-create " *straight-proc-stdout*"))
1129
+ (stderr (get-buffer-create " *straight-proc-stderr*"))
1130
+ (_ (dolist (buf (list stdout stderr))
1131
+ (let ((proc (get-buffer-process buf)))
1132
+ (ignore-errors
1133
+ (set-process-filter proc #'ignore)
1134
+ (kill-process proc)))))
1135
+ (stdout-proc (make-process
1136
+ :name "straight"
1137
+ :command (cons program args)
1138
+ :buffer stdout
1139
+ :stderr stderr
1140
+ :noquery t))
1141
+ (stderr-proc (get-buffer-process stderr)))
1142
+ (dolist (buf (list stdout stderr))
1143
+ (with-current-buffer buf
1144
+ (erase-buffer))
1145
+ (let ((proc (get-buffer-process buf)))
1146
+ (set-process-filter proc #'straight--process-filter)
1147
+ (set-process-sentinel proc #'ignore)
1148
+ (set-process-plist
1149
+ proc (plist-put (process-plist proc) :stdin stdout-proc))))
1150
+ (setq straight--process-output-counter 0)
1151
+ (let ((last-output-counter -1))
1152
+ (while (or (process-live-p stdout-proc)
1153
+ (process-live-p stderr-proc)
1154
+ (/= straight--process-output-counter
1155
+ last-output-counter))
1156
+ ;; Save the previous value of the output counter, then run
1157
+ ;; `accept-process-output'. If the counter doesn't increase
1158
+ ;; when doing so, then no output was received, which according
1159
+ ;; to the API of `accept-process-output' means that the
1160
+ ;; connection must be closed, and we are done.
1161
+ (setq last-output-counter straight--process-output-counter)
1162
+ (accept-process-output stdout-proc)
1163
+ (accept-process-output stderr-proc)))
1164
+ (list
1165
+ (process-exit-status stdout-proc)
1166
+ (with-current-buffer stdout
1167
+ (buffer-string))
1168
+ (with-current-buffer stderr
1169
+ (buffer-string)))))
1170
+
1052
1171
(defun straight--process-call (program &rest args)
1053
- "Run PROGRAM syncrhonously with ARGS.
1172
+ "Run PROGRAM synchronously with ARGS.
1054
1173
Return a list of form: (EXITCODE STDOUT STDERR).
1055
1174
If the process is unable to start, return an elisp error object."
1056
1175
(when (string-match-p "/" program)
1057
1176
(setq program (expand-file-name program)))
1058
- (condition-case e
1059
- (with-temp-buffer
1060
- (list
1061
- (apply #'call-process program nil
1062
- (list t straight--process-stderr)
1063
- nil args)
1064
- (let ((s (buffer-substring-no-properties (point-min) (point-max))))
1065
- (unless (string-empty-p s) s))
1066
- (progn
1067
- (insert-file-contents
1068
- straight--process-stderr nil nil nil 'replace)
1177
+ (if straight-display-subprocess-prompts
1178
+ (apply #'straight--process-call-interactively program args)
1179
+ (condition-case e
1180
+ (with-temp-buffer
1181
+ (list
1182
+ (apply #'call-process program nil
1183
+ (list t straight--process-stderr)
1184
+ nil args)
1069
1185
(let ((s (buffer-substring-no-properties (point-min) (point-max))))
1070
- (unless (string-empty-p s) s)))))
1071
- (error e)))
1186
+ (unless (string-empty-p s) s))
1187
+ (progn
1188
+ (insert-file-contents
1189
+ straight--process-stderr nil nil nil 'replace)
1190
+ (let ((s (buffer-substring-no-properties
1191
+ (point-min) (point-max))))
1192
+ (unless (string-empty-p s) s)))))
1193
+ (error e))))
1072
1194
1073
1195
(defmacro straight--process-with-result (result &rest body)
1074
1196
"Provide anaphoric RESULT bindings for duration of BODY.
0 commit comments