Skip to content

Commit

Permalink
external-editor: Major refactor.
Browse files Browse the repository at this point in the history
Delete move-caret-to-end.  No longer needed since its logic is covered by
ffi-buffer-paste.

Update comments.
  • Loading branch information
aadcg committed Oct 20, 2023
1 parent 14608a0 commit 9478440
Showing 1 changed file with 41 additions and 80 deletions.
121 changes: 41 additions & 80 deletions source/external-editor.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -3,108 +3,69 @@

(in-package :nyxt)

(defun %edit-with-external-editor (&optional input-text)
"Edit `input-text' using `external-editor-program'.
(defun %edit-with-external-editor (content)
"Edit CONTENT using `external-editor-program'.
Create a temporary file and return its content. The editor runs synchronously
so invoke on a separate thread when possible."
(uiop:with-temporary-file (:directory (files:expand (make-instance 'nyxt-data-directory))
:pathname p)
(when (> (length input-text) 0)
(with-open-file (f p :direction :io
:if-exists :append)
(write-sequence input-text f)))
(log:debug "External editor ~s opens ~s"
(external-editor-program *browser*) p)
(with-protect ("Failed editing: ~a" :condition)
(uiop:run-program (append (uiop:ensure-list (external-editor-program *browser*))
(list (uiop:native-namestring p)))
:ignore-error-status t))
(uiop:read-file-string p)))
(with-accessors ((cmd external-editor-program)) *browser*
(uiop:with-temporary-file (:directory (files:expand (make-instance 'nyxt-data-directory))
:pathname p)
(with-open-file (f p :direction :io :if-exists :append) (write-sequence content f))
(log:debug "External editor ~s opens ~s" cmd p)
(with-protect ("Failed editing: ~a" :condition)
(uiop:run-program `(,@cmd ,(uiop:native-namestring p))
:ignore-error-status t))
(uiop:read-file-string p))))

;; BUG: Fails when the input field loses its focus, e.g the DuckDuckGo search
;; bar. A possible solution is to keep track of last focused element for each
;; buffer.
(define-parenscript select-input-field ()
(let ((active-element (nyxt/ps:active-element document)))
(when (nyxt/ps:element-editable-p active-element)
(ps:chain active-element (select)))))

(define-parenscript move-caret-to-end ()
;; Inspired by https://stackoverflow.com/questions/4715762/javascript-move-caret-to-last-character.
(let ((el (nyxt/ps:active-element document)))
(if (string= (ps:chain (typeof (ps:@ el selection-start)))
"number")
(progn
(setf (ps:chain el selection-end)
(ps:chain el value length))
(setf (ps:chain el selection-start)
(ps:chain el selection-end)))
(when (not (string= (ps:chain (typeof (ps:@ el create-text-range)))
"undefined"))
(ps:chain el (focus))
(let ((range (ps:chain el (create-text-range))))
(ps:chain range (collapse false))
(ps:chain range (select)))))))

;; TODO:

;; BUG: Fails when the input field loses its focus, e.g the DuckDuckGo search
;; bar. Can probably be solved with JS.

;; There could be an optional exiting behavior -- set-caret-on-end or
;; undo-selection.

;; (define-parenscript undo-selection ()
;; (ps:chain window (get-selection) (remove-all-ranges)))

;; It could be extended so that the coordinates of the cursor (line,column)
;; could be shared between Nyxt and the external editor. A general solution
;; can't be achieved since not all editors, e.g. vi, accept the syntax
;; `+line:column' as an option to start the editor.

(define-command-global edit-with-external-editor ()
"Edit the current input field using `external-editor-program'."
(if (external-editor-program *browser*)
(run-thread "external editor"
(select-input-field)
(ffi-buffer-paste (current-buffer) (%edit-with-external-editor (ffi-buffer-copy (current-buffer))))
(move-caret-to-end))
(ffi-buffer-paste (current-buffer)
(%edit-with-external-editor (ffi-buffer-copy (current-buffer)))))
(echo-warning "Please set `external-editor-program' browser slot.")))

;; Should belong to user-files.lisp but the define-command-global macro is
;; defined later.
(define-command-global edit-user-file-with-external-editor ()
"Edit the queried user file using `external-editor-program'.
If the user file is GPG-encrypted, the editor must be capable of decrypting it."
(if (external-editor-program *browser*)
(let* ((file (prompt1 :prompt "Edit user file in external editor"
:sources 'user-file-source))
(path (files:expand file)))

(echo "Using \"~{~a~^ ~}\" to edit ~s." (external-editor-program *browser*) path)
(uiop:launch-program `(,@(uiop:ensure-list (external-editor-program *browser*))
,(uiop:native-namestring path))))
(echo-warning "Please set `external-editor-program' browser slot.")))
(alex:if-let ((cmd (external-editor-program *browser*))
(path (files:expand (prompt1 :prompt "Edit user file in external editor"
:sources 'user-file-source))))
(progn
(echo "Issued \"~{~a~^ ~}\" to edit ~s." cmd path)
(uiop:run-program `(,@cmd ,(uiop:native-namestring path))
:ignore-error-status t))
(echo-warning "Please set `external-editor-program' browser slot.")))

(defun %view-source-with-external-editor ()
"View page source using `external-editor-program'.
Create a temporary file. The editor runs synchronously so invoke on a
separate thread when possible."
(let ((page-source (if (web-buffer-p (current-buffer))
(plump:serialize (document-model (current-buffer)) nil)
(ffi-buffer-get-document (current-buffer)))))
(defun %view-source-with-external-editor (page)
"Edit PAGE source using `external-editor-program'.
Create a temporary file. The editor runs synchronously so invoke on a separate
thread when possible."
(with-accessors ((cmd external-editor-program)) *browser*
(uiop:with-temporary-file (:directory (files:expand (make-instance 'nyxt-data-directory))
:pathname p)
(if (> (length page-source) 0)
(progn
(alexandria:write-string-into-file page-source p :if-exists :supersede)
(log:debug "External editor ~s opens ~s"
(external-editor-program *browser*) p)
(with-protect ("Failed editing: ~a" :condition)
(uiop:run-program (append (uiop:ensure-list (external-editor-program *browser*))
(list (uiop:native-namestring p)))
:ignore-error-status t)))
(echo-warning "Nothing to edit.")))))
(alexandria:write-string-into-file page p :if-exists :supersede)
(log:debug "External editor ~s opens ~s" cmd p)
(with-protect ("Failed editing: ~a" :condition)
(uiop:run-program `(,@cmd ,(uiop:native-namestring p))
:ignore-error-status t)))))

(define-command-global view-source-with-external-editor ()
"Edit the current page source using `external-editor-program'.
Has no effect on the page, use only to look at sources!"
(define-command-global view-source-with-external-editor (&optional (buffer (current-buffer)))
"View the current page source using `external-editor-program'."
(if (external-editor-program *browser*)
(run-thread "source viewer"
(%view-source-with-external-editor))
(%view-source-with-external-editor (if (web-buffer-p buffer)
(plump:serialize (document-model buffer) nil)
(ffi-buffer-get-document buffer))))
(echo-warning "Please set `external-editor-program' browser slot.")))

0 comments on commit 9478440

Please sign in to comment.