From c244ba9e0fb2a5963a9b89e2baf2f1759c65e721 Mon Sep 17 00:00:00 2001 From: vindarel Date: Thu, 21 Sep 2023 23:55:22 +0200 Subject: [PATCH 1/2] legit: write commit message in a buffer --- lem.asd | 3 +- src/commands/window.lisp | 1 + src/ext/legit/legit-commit.lisp | 125 ++++++++++++++++++++++++++++++++ src/ext/legit/legit.lisp | 55 +++++++++++--- 4 files changed, 173 insertions(+), 11 deletions(-) create mode 100644 src/ext/legit/legit-commit.lisp diff --git a/lem.asd b/lem.asd index 84dc647f0..41d624e12 100644 --- a/lem.asd +++ b/lem.asd @@ -182,7 +182,8 @@ :components ((:file "porcelain") (:file "peek-legit") (:file "legit") - (:file "legit-rebase"))) + (:file "legit-rebase") + (:file "legit-commit"))) (:module "scripts" :components ((:static-file "dumbrebaseeditor.sh"))))) diff --git a/src/commands/window.lisp b/src/commands/window.lisp index f4929436d..d4783e5d8 100644 --- a/src/commands/window.lisp +++ b/src/commands/window.lisp @@ -10,6 +10,7 @@ :split-active-window-vertically :split-active-window-horizontally :next-window + :previous-window :window-move-up :window-move-down :window-move-right diff --git a/src/ext/legit/legit-commit.lisp b/src/ext/legit/legit-commit.lisp new file mode 100644 index 000000000..6c07a8a48 --- /dev/null +++ b/src/ext/legit/legit-commit.lisp @@ -0,0 +1,125 @@ +(in-package :lem/legit) + +#| +Done: + +- "c" opens a commit message window on the right side, we can type a long message, +- lines starting by a # are ignored. + +TODOs: + +- add C-c C-s to sign the message +- and other shortcuts and features + +Future: + +- save previous messages, add C-p C-n commands to show them +- find related Github issue when writing "fixes #" + +|# + +(define-major-mode legit-commit-mode lem-markdown-mode:markdown-mode + (:name "legit-commit-mode" + :syntax-table lem-markdown-mode::*markdown-syntax-table* + :keymap *legit-commit-mode-keymap*) + ;; no syntax highlihgt in fact. + (setf (variable-value 'enable-syntax-highlight) t)) + +;; Validate, abort. +(define-key *legit-commit-mode-keymap* "C-c C-c" 'commit-continue) +(define-key *legit-commit-mode-keymap* "C-Return" 'commit-continue) +(define-key *legit-commit-mode-keymap* "M-q" 'commit-abort) +(define-key *legit-commit-mode-keymap* "C-c C-k" 'commit-abort) + +;; Navigation. +;; find and display the previous commit messages. +;; (define-key *legit-commit-mode-keymap* "C-n" 'next-commit) +;; (define-key *legit-commit-mode-keymap* "C-p" 'previous-commit) + +;; Help. +(define-key *legit-commit-mode-keymap* "C-x ?" 'commit-help) + +(defun commit () + (let ((buffer (make-buffer "*legit-commit*"))) + (setf (buffer-directory buffer) (uiop:getcwd)) + (setf (buffer-read-only-p buffer) nil) + (erase-buffer buffer) + (move-to-line (buffer-point buffer) 1) + (insert-string (buffer-point buffer) "write commit message:") + (change-buffer-mode buffer 'legit-commit-mode) + ;; (setf (buffer-read-only-p buffer) t) + (move-to-line (buffer-point buffer) 1))) + +(defun clean-commit-message (text) + "Remove lines starting with a #." + ;; We should collect meaningful data too, like a signature. + (loop for line in (str:lines text) + unless (str:starts-with-p "#" line) + collect line into result + finally (return (str:unlines result)))) + +;; void message: +#+(or) +(assert (str:blankp (clean-commit-message +"# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +"))) + +;; a message on the first line: +#+(or) +(assert (equal "test message" (clean-commit-message +"test message +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +"))) + +;; a few lines: +#+(or) +(assert (equal "one + +two +" (clean-commit-message +"one + +two + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +"))) + +(defun commit-continue () + (let* ((message (buffer-text (make-buffer "*legit-commit*"))) + (cleaned-message (clean-commit-message message))) + (cond + ((str:blankp cleaned-message) + (message "No commit message, do nothing.")) + (t + (with-current-project () + (run-function (lambda () + (lem/porcelain::commit cleaned-message)) + :message "commited") + (kill-buffer "*legit-commit*") + ;; come back on the status on the left: + (lem-core/commands/window:previous-window) + ;; and refresh. + (legit-status)))))) + +(define-command commit-abort () () + (kill-buffer "*legit-commit*") + (lem-core/commands/window:previous-window)) + +(define-command commit-abort-yes-or-no () () + ;; TODO: prompt for confirmation. + (kill-buffer "*legit-commit*")) + +(define-command commit-help () () + "Show the important keybindings." + (with-pop-up-typeout-window (s (make-buffer "*legit-help*") :erase t) + (format s "Legit commit.~&") + (format s "~%") + (format s "Commands:~&") + (format s "Validate: C-Return, C-c C-c~&") + (format s "Stop and quit: Escape, M-q.~&") + (format s "~%") + (format s "Show this help: C-x ? or ?, M-x legit-commit-help") + )) diff --git a/src/ext/legit/legit.lisp b/src/ext/legit/legit.lisp index bec77dafb..649ff33e0 100644 --- a/src/ext/legit/legit.lisp +++ b/src/ext/legit/legit.lisp @@ -25,6 +25,7 @@ Done: - branch checkout, branch create&checkout - view commit at point - basic Fossil support (current branch, add change, commit) +- redact a proper commit text in its own buffer, not only a one liner. TODO: @@ -37,7 +38,6 @@ Ongoing: Nice to have/todo next: - view log -- redact a proper commit text, not only one line - other VCS support Next: @@ -161,7 +161,7 @@ Next: (setf (buffer-read-only-p buffer) t) (move-to-line (buffer-point buffer) 1))) -(defun make-move-function (file &key cached) +(defun make-diff-function (file &key cached) (lambda () (with-current-project () (show-diff (lem/porcelain:file-diff file :cached cached))))) @@ -349,12 +349,47 @@ Next: point start)))) +(defparameter *commit-buffer-message* + "~%# Please enter the commit message for your changes.~%~ + # Lines starting with '#' will be discarded, and an empty message does nothing.~%~ + # Validate with C-c C-c, quit with M-q or C-c C-k") + (define-command legit-commit () () - (let ((message (prompt-for-string "Commit message: "))) - (with-current-project () - (lem/porcelain:commit message) - (legit-status) - (message "Commited.")))) + "Write a commit message in its dedicated buffer. + + In this buffer, use C-c to validate, M-q or C-c C-k to quit." + + ;; The git command accepts a commit message as argument (-m), + ;; but also a simple "git commit" starts an editing process, with a pre-formatted + ;; help text. As with the interactive rebase process, we would need to: + ;; - start the commit process with a dummy editor, + ;; - on validation kill the dummy editor and let the git process continue (at this moment git itself decides to validate or to ignore the message). + ;; This is used by Magit, and we do this for the interactive rebase, but: + ;; - our dummy editor script doesn't support windows (still as of <2023-09-22 Fri>). + ;; + ;; So we go with a simpler, cross-platform and pure Lem/Lisp workflow: + ;; - create a Lem buffer, add some help text + ;; - on validation, check ourselves that the message isn't void, extract other information (signature…) and run the commit, with the -m argument. + + (let ((buffer (make-buffer "*legit-commit*"))) + (setf (buffer-directory buffer) (buffer-directory)) + (setf (buffer-read-only-p buffer) nil) + (erase-buffer buffer) + (move-to-line (buffer-point buffer) 1) + (insert-string (buffer-point buffer) + (format nil *commit-buffer-message*)) + (change-buffer-mode buffer 'legit-commit-mode) + (move-to-line (buffer-point buffer) 1) + + ;; The Legit command, like grep, creates its own window. + ;; Where is it best to show the commit buffer? + ;; 1) quit the legit view altogether, open the commit buffer in full height: + ;; (lem/legit::legit-quit) + ;; 2) open the commit buffer on the left instead of legit status (and nice to have: show the full changes on the right) + ;; (setf (not-switchable-buffer-p (current-buffer)) nil) + ;; 3) open the commit buffer on the right, don't touch the ongoing legit status. + (next-window) + (switch-to-buffer buffer))) (define-command legit-status () () @@ -376,7 +411,7 @@ Next: (if untracked-files (loop :for file :in untracked-files :do (lem/peek-legit:with-appending-source - (point :move-function (make-move-function file) + (point :move-function (make-diff-function file) :visit-file-function (make-visit-file-function file) :stage-function (make-stage-function file) :unstage-function (lambda () (message "File is not tracked, can't be unstaged."))) @@ -389,7 +424,7 @@ Next: (if unstaged-files (loop :for file :in unstaged-files :do (lem/peek-legit:with-appending-source - (point :move-function (make-move-function file) + (point :move-function (make-diff-function file) :visit-file-function (make-visit-file-function file) :stage-function (make-stage-function file) :unstage-function (make-unstage-function file :already-unstaged t)) @@ -406,7 +441,7 @@ Next: (loop :for file :in staged-files :for i := 0 :then (incf i) :do (lem/peek-legit:with-appending-source - (point :move-function (make-move-function file :cached t) + (point :move-function (make-diff-function file :cached t) :visit-file-function (make-visit-file-function file) :stage-function (make-stage-function file) :unstage-function (make-unstage-function file)) From cfd1d54663ec9829257d5b3da0a246d58a7f6fc8 Mon Sep 17 00:00:00 2001 From: vindarel Date: Mon, 2 Oct 2023 17:48:58 +0200 Subject: [PATCH 2/2] legit commit: confirm abort, add parameter to bypass prompt --- src/ext/legit/legit-commit.lisp | 40 +++++++++++++++------------------ 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/ext/legit/legit-commit.lisp b/src/ext/legit/legit-commit.lisp index 6c07a8a48..cb3f26899 100644 --- a/src/ext/legit/legit-commit.lisp +++ b/src/ext/legit/legit-commit.lisp @@ -3,7 +3,8 @@ #| Done: -- "c" opens a commit message window on the right side, we can type a long message, +- "c" opens a commit message window on the right side, we can type a long message + - this command is defined in legit.lisp. - lines starting by a # are ignored. TODOs: @@ -18,6 +19,7 @@ Future: |# +;; major-mode for the commit buffer. (define-major-mode legit-commit-mode lem-markdown-mode:markdown-mode (:name "legit-commit-mode" :syntax-table lem-markdown-mode::*markdown-syntax-table* @@ -25,31 +27,24 @@ Future: ;; no syntax highlihgt in fact. (setf (variable-value 'enable-syntax-highlight) t)) -;; Validate, abort. +;; User parameters. +(defparameter *prompt-for-commit-abort-p* t + "If non t, abort the current commit message without asking for confirmation.") + +;; Keys: +;; validate, abort. (define-key *legit-commit-mode-keymap* "C-c C-c" 'commit-continue) (define-key *legit-commit-mode-keymap* "C-Return" 'commit-continue) (define-key *legit-commit-mode-keymap* "M-q" 'commit-abort) (define-key *legit-commit-mode-keymap* "C-c C-k" 'commit-abort) -;; Navigation. -;; find and display the previous commit messages. +;; Nice to have: find and display the previous commit messages. ;; (define-key *legit-commit-mode-keymap* "C-n" 'next-commit) ;; (define-key *legit-commit-mode-keymap* "C-p" 'previous-commit) ;; Help. (define-key *legit-commit-mode-keymap* "C-x ?" 'commit-help) -(defun commit () - (let ((buffer (make-buffer "*legit-commit*"))) - (setf (buffer-directory buffer) (uiop:getcwd)) - (setf (buffer-read-only-p buffer) nil) - (erase-buffer buffer) - (move-to-line (buffer-point buffer) 1) - (insert-string (buffer-point buffer) "write commit message:") - (change-buffer-mode buffer 'legit-commit-mode) - ;; (setf (buffer-read-only-p buffer) t) - (move-to-line (buffer-point buffer) 1))) - (defun clean-commit-message (text) "Remove lines starting with a #." ;; We should collect meaningful data too, like a signature. @@ -87,7 +82,10 @@ two # with '#' will be ignored, and an empty message aborts the commit. "))) -(defun commit-continue () +(define-command commit-continue () () + "If the commit message is non-empty, commit, kill the commit buffer and come back to the legit status window. + + Lines starting with '#' are ignored." (let* ((message (buffer-text (make-buffer "*legit-commit*"))) (cleaned-message (clean-commit-message message))) (cond @@ -105,12 +103,10 @@ two (legit-status)))))) (define-command commit-abort () () - (kill-buffer "*legit-commit*") - (lem-core/commands/window:previous-window)) - -(define-command commit-abort-yes-or-no () () - ;; TODO: prompt for confirmation. - (kill-buffer "*legit-commit*")) + (when (or (not *prompt-for-commit-abort-p*) + (prompt-for-y-or-n-p "Abort commit?")) + (lem-core/commands/window:previous-window) + (kill-buffer "*legit-commit*"))) (define-command commit-help () () "Show the important keybindings."