Re: Magit-inspired Emacs interface for Sapling (sl)

Philip Kaludercic <[email protected]>
Newsgroups gmane.emacs.devel
Message-ID <[email protected]>
I have a few comments on the code here:


A general point: You are presenting the package as Magit-inspired, but
do not use transient?  Have you considered implementing the VC interface
and then sparing the need to re-implement an entire new interface (also,
is vc-git any better on your system)?

swithin chan <[email protected]> writes:

> 1. I have been frustrated by the fact that git is slow on windows, I
> know corporate / commercial things a not good idea for the open source
> community, but every time I run emacs, even though i switch to
> gccemacs, the magit package took fifty two seconds, which slows me
> down, against vscode or other editor. I debugged, then I know is
> because of windows process is different from Linux. I used daemon to
> let startup time be quicker,I used eshell ,  but I still cannot push
> down the speed using version control. 
>
> Rust jujitsu need to install rust, but I need to seek approval and is
> gigantic. Sapling is on git (as you said GPLv2) and is much smaller in
> size, easier to run on windows.
> https://github.com/facebook/sapling/blob/main/LICENSE
> I am not promoting commercial ware, I am just make working emacs in windows a more comfortable environment. 

The phrase "commercial ware" doesn't really mean anything here, and as I
said before, that is not an issue as long as the license grants the user
the necessary freedoms.

> Sent from Yahoo Mail for iPhone
>
>
> On Friday, August 14, 2026, 11:58 PM, Philip Kaludercic <[email protected]> wrote:
>
>> swithin chan <[email protected]> writes:
>>
>>> Dear Team, 
>>> Sorry in a corporate environment i must use windows to do version
>>> control. But git is very slow (whether in mingw/bin/git, cmd/bin/git
>>> or /bin/git); i finally decided to use meta's sapling version control
>>> package because this is very small only 30mb. i am sorry for using
>>> something from commercial company but i really need to do version
>>> control quickly. 
>>
>> That is not an issue, the main question is if https://sapling-scm.com/
>> is free software, which is the case (GPLv2).
>>
>>>                  i burnt some tokens and make the sapling interface
>>> called sl.el. hope to seek approval to put on non-gnu elpa. if you
>>> guys can put on gnu elpa i would be very grateful. Thank you!!!!
>>> Emacs FANSSwithin Chan https://github.com/swithinchan/sl 
>>> sl.el
>>
>> I am afraid that we cannot add the package to GNU ELPA, since the
>> copyright situation is tricky given that you LLM generated the code.
>>
>> But given that it is outright generated code, I would like to review it
>> before admitting it.  I can do so over the weekend, would you be OK with
>> merging the changes and addressing the questions I have?
>>
>>> A fast, Magit-inspired Emacs interface for Sapling(sl). It is designed
>>> to feel familiar to Magit users while being intentionallysmall and
>>> Windows-friendly.
>>
>> Can you elaborate why it is windows friendly?
(unnamed) (text/x-patch, 18.6 KB)
diff --git a/sl.el b/sl.el
index a0b5f0d..c5dec05 100644
--- a/sl.el
+++ b/sl.el
@@ -53,111 +53,87 @@
 
 (defcustom sl-program "sl"
   "Name of or path to the Sapling executable."
-  :type 'string
-  :group 'sl)
+  :type 'string)
 
 (defcustom sl-status-buffer-name "*sl*"
   "Name of the Sapling status buffer."
-  :type 'string
-  :group 'sl)
+  :type 'string)
 
 (defcustom sl-smartlog-buffer-name "*sl-smartlog*"
   "Name of the Sapling smartlog buffer."
-  :type 'string
-  :group 'sl)
-
-(defcustom sl-log-buffer-name "*sl-log*"
-  "Name of the Sapling graph log buffer."
-  :type 'string
-  :group 'sl)
+  :type 'string)
 
 (defcustom sl-diff-buffer-name "*sl-diff*"
   "Name of the Sapling diff buffer."
-  :type 'string
-  :group 'sl)
+  :type 'string)
 
 (defcustom sl-output-buffer-name "*sl-output*"
   "Name of the generic Sapling output buffer."
-  :type 'string
-  :group 'sl)
+  :type 'string)
 
 (defcustom sl-log-limit 100
   "Number of commits to show in `sl-log'."
-  :type 'integer
-  :group 'sl)
+  :type 'natnum)
 
-(defcustom sl-w32-pipe-read-delay 0
+(defcustom sl-w32-pipe-read-delay (or (bound-and-true-p w32-pipe-read-delay) 0)
   "Value for `w32-pipe-read-delay' while reading `sl' output.
 Lower values make process output significantly faster on Windows."
-  :type 'integer
-  :group 'sl)
+  :type 'integer)
 
 (defcustom sl-use-color t
   "When non-nil, colorize Sapling command output.
 Sl commands that display text are run with ANSI color enabled and
 `ansi-color' translates the SGR sequences into Emacs faces.  This
 works on Windows as well as on Unix."
-  :type 'boolean
-  :group 'sl)
+  :type 'boolean)
 
 (defcustom sl-diff-use-diff-mode t
   "When non-nil, show `sl-diff' output in `diff-mode'.
 This gives Emacs-native diff coloring (removed lines in red, added
 lines in green) without parsing Sapling's terminal color codes."
-  :type 'boolean
-  :group 'sl)
+  :type 'boolean)
 
 (defface sl-header-face
   '((t :inherit bold))
-  "Face for Sapling section headers."
-  :group 'sl)
+  "Face for Sapling section headers.")
 
 (defface sl-status-modified-face
   '((t :foreground "orange"))
-  "Face for modified files."
-  :group 'sl)
+  "Face for modified files.")
 
 (defface sl-status-added-face
-  '((t :foreground "green"))
-  "Face for added files."
-  :group 'sl)
+  '((t :foreground "green"))	 ;perhaps inherit from diff-mode faces?
+  "Face for added files.")
 
 (defface sl-status-removed-face
   '((t :foreground "red"))
-  "Face for removed files."
-  :group 'sl)
+  "Face for removed files.")
 
 (defface sl-status-missing-face
   '((t :foreground "red" :weight bold))
-  "Face for missing files."
-  :group 'sl)
+  "Face for missing files.")
 
 (defface sl-status-unknown-face
   '((t :foreground "magenta"))
-  "Face for untracked files."
-  :group 'sl)
+  "Face for untracked files.")
 
 (defface sl-status-ignored-face
   '((t :foreground "gray"))
-  "Face for ignored files."
-  :group 'sl)
+  "Face for ignored files.")
 
 (defface sl-status-clean-face
   '((t :foreground "gray"))
-  "Face for clean files."
-  :group 'sl)
+  "Face for clean files.")
 
 (defface sl-marked-face
   '((t :weight bold :box (:line-width 1)))
-  "Face for marked files in the status buffer."
-  :group 'sl)
+  "Face for marked files in the status buffer.")
 
 (defface sl-log-changeset-face
   '((t :foreground "yellow"))
-  "Face for changeset identifiers in Sapling smartlog output."
-  :group 'sl)
+  "Face for changeset identifiers in Sapling smartlog output.")
 
-;;; Buffer-local state
+;;;; Buffer-local state
 
 (defvar-local sl--repo-root nil
   "Root directory of the Sapling repository for the current buffer.")
@@ -188,7 +164,7 @@ lines in green) without parsing Sapling's terminal color codes."
 
 ;;; Process helpers
 
-(defun sl--windows-p ()
+(defun sl--windows-p ()		 ;this is not used?
   "Return non-nil when running on a Windows system."
   (memq system-type '(ms-dos windows-nt cygwin)))
 
@@ -202,8 +178,8 @@ lines in green) without parsing Sapling's terminal color codes."
   "Return a process command list for running `sl' with ARGS.
 On Windows, `.bat'/`.cmd' wrappers need to be run through the shell."
   (let* ((program (sl--sl-program))
-         (extension (downcase (or (file-name-extension program) ""))))
-    (if (member extension '("bat" "cmd" "com"))
+         (extension (file-name-extension program)))
+    (if (member-ignore-case extension '("bat" "cmd" "com"))
         (list shell-file-name shell-command-switch
               (mapconcat #'shell-quote-argument (cons program args) " "))
       (cons program args))))
@@ -219,6 +195,7 @@ where Sapling may otherwise use the native console color API."
 When `sl-use-color' is non-nil, allow colored output under Sapling's
 automation mode."
   (let ((env (copy-sequence process-environment)))
+    ;; a comment explaining these variables would be useful.
     (dolist (var '("HGPLAIN=1" "SL_AUTOMATION=1"))
       (unless (member var env)
         (push var env)))
@@ -227,6 +204,7 @@ automation mode."
         (push "SL_AUTOMATION_EXCEPT=color" env)))
     env))
 
+;; please fix the checkdoc error!
 (cl-defun sl--run-async (args &key name callback directory color)
   "Run `sl' with ARGS asynchronously.
 
@@ -240,6 +218,10 @@ enabled."
          (process-environment (sl--process-environment)))
     ;; `w32-pipe-read-delay' is a global variable, so bindings around
     ;; `make-process' do not affect later reads.  Set it directly here.
+    ;;
+    ;; This seems rather invasive, and something that the user should
+    ;; be made aware of in some sense, if it were to happen to break
+    ;; some other scripts.
     (when (and (boundp 'w32-pipe-read-delay)
                sl-w32-pipe-read-delay)
       (setq w32-pipe-read-delay sl-w32-pipe-read-delay))
@@ -267,6 +249,9 @@ enabled."
          (when callback
            (funcall callback out code)))))))
 
+;; I would suggest making the signature (directory &rest args)
+;;
+;; Or perhaps turn this into a macro (sl--with-output (dir &rest args) &rest body) so that you don't have to turn everything into a string
 (defun sl--call-output (args &optional directory)
   "Run `sl' with ARGS synchronously in DIRECTORY and return its output.
 Signal an error if the command exits unsuccessfully."
@@ -341,7 +326,6 @@ Signal an error if the command exits unsuccessfully."
   "Major mode for Sapling status output.
 
 \\{sl-mode-map}"
-  :group 'sl
   (setq-local buffer-read-only t)
   (setq-local truncate-lines t)
   (setq-local revert-buffer-function #'sl-refresh)
@@ -367,12 +351,12 @@ Signal an error if the command exits unsuccessfully."
           sl--smartlog nil)
     (sl-refresh)))
 
-(defun sl-quit ()
+(defun sl-quit ()		 ;why are you redefiniting this?
   "Quit the current Sapling buffer."
   (interactive)
   (quit-window))
 
-(defun sl-help ()
+(defun sl-help ()		 ;here as well?
   "Show help for the current Sapling mode."
   (interactive)
   (describe-mode))
@@ -424,7 +408,7 @@ Signal an error if the command exits unsuccessfully."
            (sl--finish-refresh buffer root smartlog files info)))))))
 
 (defun sl--finish-refresh (buffer root smartlog files info)
-  "Populate BUFFER with refreshed Sapling data."
+  "Populate BUFFER with refreshed Sapling data." ;please fix the checkdoc issues!
   (when (buffer-live-p buffer)
     (with-current-buffer buffer
       (setq sl--repo-root root
@@ -442,9 +426,7 @@ Signal an error if the command exits unsuccessfully."
 
 (defun sl--strip-cr (string)
   "Return STRING without a trailing carriage return, if any."
-  (if (string-suffix-p "\r" string)
-      (substring string 0 -1)
-    string))
+  (string-trim-right string "\r"))
 
 (defun sl--parse-status (text)
   "Parse `sl status' output TEXT into a list of (STATUS FILE)."
@@ -459,6 +441,7 @@ Signal an error if the command exits unsuccessfully."
   "Parse commit info line TEXT into (HASH BOOKMARK PHASE)."
   (let ((line (sl--strip-cr (car (split-string text "\n" t)))))
     (when line
+      ;; you might be able to simplify this to (take 3 (split-string line "\t"))
       (let ((parts (split-string line "\t")))
         (list (nth 0 parts) (nth 1 parts) (nth 2 parts))))))
 
@@ -472,7 +455,7 @@ Signal an error if the command exits unsuccessfully."
     (?? 'sl-status-unknown-face)
     (?I 'sl-status-ignored-face)
     (?C 'sl-status-clean-face)
-    (t nil)))
+    (t nil)))			 ;isn't this an unexpected state?
 
 (defun sl--insert-smartlog (text)
   "Insert smartlog TEXT with changeset hashes colored."
@@ -541,8 +524,7 @@ Signal an error if the command exits unsuccessfully."
 (defun sl--goto-file (file)
   "Move point to FILE in the current status buffer."
   (goto-char (point-min))
-  (while (and (not (eobp))
-              (not (equal (get-text-property (point) 'sl-file) file)))
+  (unless (or (eobp) (equal (get-text-property (point) 'sl-file) file))
     (forward-line 1)))
 
 (defun sl-mark ()
@@ -599,8 +581,8 @@ Signal an error if the command exits unsuccessfully."
 (defvar sl-output-mode-map
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map special-mode-map)
-    (define-key map (kbd "g") #'sl-output-refresh)
-    (define-key map (kbd "q") #'quit-window)
+    (define-key map (kbd "g") #'sl-output-refresh) ;This shouldn't be necessary, as you are already setting `revert-buffer-function'
+    (define-key map (kbd "q") #'quit-window) ;you are inheriting from `special-mode', this binding is already there
     map)
   "Keymap for `sl-output-mode'.")
 
@@ -617,7 +599,6 @@ Signal an error if the command exits unsuccessfully."
   "Major mode for Sapling command output.
 
 \\{sl-output-mode-map}"
-  :group 'sl
   (setq-local buffer-read-only t)
   (setq-local truncate-lines t)
   (setq-local revert-buffer-function #'sl-output-refresh))
@@ -625,7 +606,7 @@ Signal an error if the command exits unsuccessfully."
 (defvar sl-diff-mode-map
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map diff-mode-map)
-    (define-key map (kbd "g") #'sl-output-refresh)
+    (define-key map (kbd "g") #'sl-output-refresh) ;same comments here as above, just with diff-mode
     (define-key map (kbd "q") #'quit-window)
     map)
   "Keymap for `sl-diff-mode'.")
@@ -634,9 +615,9 @@ Signal an error if the command exits unsuccessfully."
   "Major mode for Sapling diff output.
 
 \\{sl-diff-mode-map}"
-  :group 'sl
   (setq-local buffer-read-only t)
   (setq-local truncate-lines t)
+  ;; is this the only meaningful difference between this mode and `diff-mode'?
   (setq-local revert-buffer-function #'sl-output-refresh))
 
 (defun sl--render-output-loading ()
@@ -659,7 +640,7 @@ Signal an error if the command exits unsuccessfully."
   (let ((inhibit-read-only t)
         (text-beg nil))
     (erase-buffer)
-    (insert (propertize (format "%s\n" title) 'face 'sl-header-face))
+    (insert (propertize title 'face 'sl-header-face) "\n")
     (when text
       (setq text-beg (point))
       (insert text)
@@ -720,7 +701,7 @@ ANSI colors enabled."
 (defun sl--refresh-status-buffer ()
   "Refresh the Sapling status buffer if it exists."
   (let ((buffer (get-buffer sl-status-buffer-name)))
-    (when (and buffer (buffer-live-p buffer))
+    (when (and buffer (buffer-live-p buffer)) ;is this racy?
       (with-current-buffer buffer
         (when (derived-mode-p 'sl-mode)
           (sl-refresh))))))
@@ -775,7 +756,7 @@ Returns nil when the current buffer is not a status buffer."
       (push file files))
     (nreverse files)))
 
-;;; Commands
+;;;; Commands
 
 ;;;###autoload
 (defun sl-smartlog ()
@@ -874,6 +855,7 @@ called outside the status buffer."
           (when previous-message
             (insert previous-message)
             (unless (bolp) (insert "\n")))
+	  ;; iconing scissors left to right ("8<") seems slightly more intuitive to me
           (insert "\n# ------------------------ >8 ------------------------\n")
           (insert (sl--commit-comment root files amend)))
         (goto-char (point-min)))
@@ -899,6 +881,8 @@ called outside the status buffer."
   (let ((cut (save-excursion
                (goto-char (point-min))
                (re-search-forward
+		;; if you are re-using the string, you really should
+		;; move this into a constant!
                 "^# ------------------------ >8 ------------------------$" nil t))))
     (string-trim
      (if cut
@@ -948,8 +932,8 @@ called outside the status buffer."
                     (insert out))
                   (current-buffer)))))))))))
 
-(defun sl-commit-cancel ()
-  "Cancel the commit or amend in the current message buffer."
+(defun sl-commit-cancel ()	 ;again, why a new command?
+  "Cancel the commit or amend in the current message buffer." ;the doc string doesn't appear to match the implementation?
   (interactive)
   (kill-buffer))
 
@@ -1096,7 +1080,7 @@ the confirmation prompt."
   (interactive (list (sl--marked-or-point-files)))
   (when (and (null files)
              (not current-prefix-arg)
-             (not (y-or-n-p "Revert all pending changes? ")))
+             (not (y-or-n-p "Revert all pending changes? "))) ;please use `yes-or-no-p', in case people prefer verbose answers!
     (user-error "Aborted"))
   (sl--run-and-show (append '("revert") files) "Sl Revert"))
 
@@ -1106,7 +1090,7 @@ the confirmation prompt."
 With a prefix argument, print what would be deleted instead."
   (interactive "P")
   (unless dry-run
-    (unless (y-or-n-p "Delete all untracked files? ")
+    (unless (y-or-n-p "Delete all untracked files? ") ;here again, users can set `use-short-answers' if they prefer `y-or-n-p'
       (user-error "Aborted")))
   (sl--run-and-show
    (if dry-run '("clean" "--print") '("clean"))
@@ -1165,9 +1149,7 @@ With a prefix argument, print what would be deleted instead."
   "Undo the last local Sapling command.
 With a prefix argument, undo that many local commands."
   (interactive "P")
-  (let ((args (if arg
-                  (list "undo" (number-to-string (prefix-numeric-value arg)))
-                '("undo"))))
+  (let ((args (cons "undo" (and arg (list (number-to-string (prefix-numeric-value arg)))))))
     (sl--run-and-show args "Sl Undo")))
 
 ;;;###autoload
@@ -1221,8 +1203,8 @@ the file at point is used as the initial FILE."
      (list (read-string "Revision (empty for current): ") file)))
   (let* ((root (or sl--repo-root (sl--find-root default-directory)))
          (args (append '("annotate")
-                       (unless (string-empty-p revision)
-                         (list "-r" revision))
+                       (and (not (string-empty-p revision))
+                            (list "-r" revision))
                        (list file))))
     (unless root
       (user-error "Not inside a Sl repository"))
@@ -1309,72 +1291,45 @@ An empty DIRECTORY initializes the current directory."
    "Sl Init"
    default-directory))
 
+
+
 ;;;###autoload
 (defun sl-menu ()
   "Display a Magit-style dispatch menu for Sapling commands."
   (interactive)
-  (let* ((entry (read-multiple-choice
-                 "Sl"
-                 '((?s "status" "Show working copy status")
-                   (?l "smartlog" "Show smartlog")
-                   (?L "log" "Show graph log")
-                   (?d "diff" "Show diff")
-                   (?c "commit" "Commit changes")
-                   (?a "amend" "Amend current commit")
-                   (?x "absorb" "Absorb changes into stack")
-                   (?r "rebase" "Rebase onto revision")
-                   (?f "fold" "Fold commits")
-                   (?g "graft" "Graft a commit")
-                   (?h "hide" "Hide a commit")
-                   (?H "unhide" "Unhide a commit")
-                   (?z "shelve" "Shelve changes")
-                   (?Z "unshelve" "Unshelve changes")
-                   (?n "next" "Check out next commit")
-                   (?p "previous" "Check out previous commit")
-                   (?u "undo" "Undo local command")
-                   (?R "redo" "Redo local command")
-                   (?A "add" "Add files")
-                   (?D "remove" "Remove files")
-                   (?K "forget" "Forget files")
-                   (?V "revert" "Revert files")
-                   (?e "metaedit" "Edit commit message")
-                   (?o "show" "Show current commit")
-                   (?J "journal" "Show journal")
-                   (?B "bookmark" "Create bookmark")
-                   (?F "pull" "Pull changes")
-                   (?P "push" "Push changes")
-                   (?q "quit" "Quit"))))
-         (choice (if (consp entry) (car entry) entry)))
-    (cl-case choice
-      (?s (sl-status))
-      (?l (sl-smartlog))
-      (?L (sl-log))
-      (?d (sl-diff))
-      (?c (sl-commit))
-      (?a (sl-amend))
-      (?x (sl-absorb))
-      (?r (call-interactively #'sl-rebase))
-      (?f (call-interactively #'sl-fold))
-      (?g (call-interactively #'sl-graft))
-      (?h (call-interactively #'sl-hide))
-      (?H (call-interactively #'sl-unhide))
-      (?z (sl-shelve))
-      (?Z (sl-unshelve))
-      (?n (sl-next))
-      (?p (sl-previous))
-      (?u (sl-undo))
-      (?R (sl-redo))
-      (?A (sl-add))
-      (?D (sl-remove))
-      (?K (sl-forget))
-      (?V (sl-revert))
-      (?e (call-interactively #'sl-metaedit))
-      (?o (sl-show))
-      (?J (call-interactively #'sl-journal))
-      (?B (call-interactively #'sl-bookmark-create))
-      (?F (sl-pull))
-      (?P (sl-push))
-      (?q nil))))
+  (let ((table
+	 '((?s "status" "Show working copy status" sl-status)
+	   (?l "smartlog" "Show smartlog" sl-smartlog)
+	   (?L "log" "Show graph log" sl-log)
+	   (?d "diff" "Show diff" sl-diff)
+	   (?c "commit" "Commit changes" sl-commit)
+	   (?a "amend" "Amend current commit" sl-amend)
+	   (?x "absorb" "Absorb changes into stack" sl-absorb)
+	   (?r "rebase" "Rebase onto revision" sl-rebase t)
+	   (?f "fold" "Fold commits" sl-fold t)
+	   (?g "graft" "Graft a commit" sl-graft t)
+	   (?h "hide" "Hide a commit" sl-graft t)
+	   (?H "unhide" "Unhide a commit" sl-hide t)
+	   (?z "shelve" "Shelve changes" sl-shelve)
+	   (?Z "unshelve" "Unshelve changes" sl-unshelve)
+	   (?n "next" "Check out next commit" sl-next)
+	   (?p "previous" "Check out previous commit" sl-previous)
+	   (?u "undo" "Undo local command" sl-undo)
+	   (?R "redo" "Redo local command" sl-redo)
+	   (?A "add" "Add files" sl-add)
+	   (?D "remove" "Remove files" sl-remove)
+	   (?K "forget" "Forget files" sl-forget)
+	   (?V "revert" "Revert files" sl-revert)
+	   (?e "metaedit" "Edit commit message" sl-metaedit t)
+	   (?o "show" "Show current commit" sl-show)
+	   (?J "journal" "Show journal" sl-journal t)
+	   (?B "bookmark" "Create bookmark" sl-bookmark-create t)
+	   (?F "pull" "Pull changes" sl-pull)
+	   (?P "push" "Push changes" sl-push)
+	   (?q "quit" "Quit"))))
+    (pcase-exhaustive (read-multiple-choice "Sl" table)
+      (`(,_ ,_ ,_ ,fn t) (funcall-interactively fn))
+      (`(,_ ,_ ,_ ,fn) (funcall fn)))))
 
 (provide 'sl)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.