Subject: bug#77326: closed (Re: bug#77326: [PATCH] Add symbol-mode)

From: [email-masked] (GNU bug Tracking System)
Date: Tue, 15 Apr 2025 23:51:04 +0000

Your bug report

#77326: [PATCH] Add symbol-mode

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
77326: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=77326
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#77326: [PATCH] Add symbol-mode
From: Stefan Kangas <[email-masked]>
To: Gabriel Santos <[email-masked]>, Eli Zaretskii <[email-masked]>
Cc: [email-masked], [email-masked], [email-masked], [email-masked], [email-masked]
Date: Tue, 15 Apr 2025 18:50:39 -0500
List-Id: <debbugs-submit.debbugs.gnu.org>

Gabriel Santos <[email-masked]> writes:

>>So what do you suggest we do with this issue?  Are you still offering
>>this mode for addition to Emacs?
>
> Since superword-mode already does what this mode does, I think the mode
> should be dropped.

I'm therefore closing this bug report.

If this conclusion is incorrect and this should remain open, please
reply to this email (use "Reply to all" in your email client) and we can
reopen the bug report.

Subject: [PATCH] Add symbol-mode
From: Gabriel Santos <[email-masked]>
To: [email-masked]
Cc: Gabriel Santos <[email-masked]>
Date: Thu, 27 Mar 2025 19:44:21 -0300
List-Id: <debbugs-submit.debbugs.gnu.org>

Greetings,

This patch provides a new package for interacting with symbols, based on
the current word commands and the built-in package "subword".

A minor mode that replaces the word commands is also provided, also
inspired by the subword package, but additionally on the usage of
defvar-keymap I made in a PR I opened for the puni package[1].

- Points of discussion

Would the mention of this mode be a good addition to the documentation?

I don't know how to contribute to the docs, but I would look into doing
so if you think it's a good idea

[1] <https://github.com/AmaiKinono/puni/pull/75>

P.S.: This is being sent from a different e-mail from the patch because
my e-mail provider, viz. Disroot, is having issues with spam, and that
was blocking my messages, so I decided to create a Gmail account to go
around that.

Regards,

-- 
Gabriel Santos

>From ea08a5251878e5de1b379becfcef401a4393fa49 Mon Sep 17 00:00:00 2001
From: Gabriel Santos <[email-masked]>
Date: Mon, 24 Mar 2025 20:18:06 -0300
Subject: [PATCH] Add symbol-mode

* lisp/progmodes/symbol.el: New file.
* test/lisp/progmodes/symbol-tests.el: New test file.
* etc/NEWS: Announce symbol-mode.
---
 etc/NEWS                            |   9 ++
 lisp/progmodes/symbol.el            | 222 ++++++++++++++++++++++++++++
 test/lisp/progmodes/symbol-tests.el |  84 +++++++++++
 3 files changed, 315 insertions(+)
 create mode 100644 lisp/progmodes/symbol.el
 create mode 100644 test/lisp/progmodes/symbol-tests.el

diff --git a/etc/NEWS b/etc/NEWS
index d90836b8c6d..8c70bf2e648 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1651,6 +1651,15 @@ A major mode based on the tree-sitter library for editing "go.work"
 files.  If tree-sitter is properly set-up by the user, it can be
 enabled for files named "go.work".
 
+---
+** New minor mode 'symbol-mode'.
+This minor mode replaces word related commands such as 'upcase-word'
+with commands better suited for symbols.  A symbol is any combination
+of characters not separated by a space.  This is primarily useful for
+programming modes, where one can think of variables such as
+"this_is_a_word" as an actual word in the codebase, as it is used to
+refer to a value.
+
 
 * Incompatible Lisp Changes in Emacs 31.1
 
diff --git a/lisp/progmodes/symbol.el b/lisp/progmodes/symbol.el
new file mode 100644
index 00000000000..26ce9d70878
--- /dev/null
+++ b/lisp/progmodes/symbol.el
@@ -0,0 +1,222 @@
+;;; symbol.el --- Keymap for interacting with symbols instead of words -*- lexical-binding: t -*-
+
+;; Copyright (C) 2025 Free Software Foundation, Inc.
+
+;; Author: Gabriel Santos <[email-masked]>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package provides the `symbol' minor mode which replaces the
+;; regular commands for word movement and interaction, such as
+;; `backward-word' and `capitalize-word' with ones specialized for
+;; symbols (namely `backward-symbol' and `capitalize-symbol').
+
+;; A symbol is any sequence of characters, including words, that can
+;; compose a single "word" in a programming sense.  Thus, most
+;; combinations of words and signs should be valid.
+
+;; To make this clearer, the following are all symbols:
+;; - symbol
+;; - this-is-a-symbol
+;; - this_is_also_a_symbol
+;; - my/symbol
+;; - 1010-symbol
+;; - alsoSomeSymbol
+;; - 孙/武
+
+;; To enable `symbol-mode' automatically for a specific mode, use
+;; hooks:
+;;
+;; (add-hook 'lisp-mode-hook 'symbol-mode)
+;;
+;; Or, for the use-package inclined:
+;;
+;; (use-package symbol
+;;   :hook
+;;   (lisp-mode . symbol-mode))
+
+;;; Code:
+
+(defun backward-symbol (arg)
+  "Move point to the previous position that is the start of a symbol.
+For the definition of a symbol, see `symbol-mode'.
+With prefix argument ARG, do it ARG times if positive, or move
+forwards ARG times if negative."
+  (interactive "^p")
+  (forward-symbol (- arg)))
+
+(defun kill-symbol (arg)
+  "Kill characters forward until encountering the end of a symbol.
+For the definition of a symbol, see `symbol-mode'.
+With prefix argument ARG, do it ARG times if positive, or do it
+backwards ARG times if negative."
+  (interactive "p")
+  (kill-region (point) (progn (forward-symbol arg) (point))))
+
+(defun backward-kill-symbol (arg)
+  "Kill characters backwards until encountering the start of a symbol.
+For the definition of a symbol, see `symbol-mode'.
+With prefix argument ARG, do it ARG times if positive, or do it
+forwards ARG times if negative."
+  (interactive "p")
+  (kill-symbol (- arg)))
+
+(defun mark-symbol (&optional arg allow-extend)
+  "Set mark ARG symbols from point or move mark one symbol.
+For the definition of a symbol, see `symbol-mode'.
+When called from Elisp with ALLOW-EXTEND omitted or nil, mark is
+set ARG symbols from point.
+
+With ARG and ALLOW-EXTEND both non-nil (interactively with prefix
+argument), the place to which mark goes is the same place
+\\[forward-symbol] would move to with the same argument; if the mark is
+active, it moves ARG symbols from its current position, otherwise if is
+set ARG symbols from point.
+When invoked interactively without a prefix argument and no active
+region, mark moves one symbol forward.
+When invoked interactively without a prefix argument, and region is
+active, mark moves one symbol away of point (i.e., forward if mark is
+at or after point, back if mark is before point), thus extending the
+region by one symbol.  Since the direction of region depends on the
+relative position of mark and point, you can change the direction
+by \\[exchange-point-and-mark]."
+  (interactive "P\np")
+  (cond ((and allow-extend
+              (or (and (eq last-command this-command) (mark t))
+                  (region-active-p)))
+         (setq arg (if arg (prefix-numeric-value arg)
+                     (if (< (mark) (point)) -1 1)))
+         (set-mark
+          (save-excursion
+            (goto-char (mark))
+            (forward-symbol arg)
+            (point))))
+        (t
+         (push-mark
+          (save-excursion
+            (forward-symbol (prefix-numeric-value arg))
+            (point))
+          nil t))))
+
+(defun right-symbol (&optional n)
+  "Move point N symbols to the right (to the left if N is negative).
+for the definition of a symbol, see `symbol-mode'.
+
+Depending on the bidirectional context, this may move either backward
+or forward in the buffer.  This is in contrast with \\[forward-symbol]
+and \\[backward-symbol], which see."
+  (interactive "^p")
+  (if (eq (current-bidi-paragraph-direction) 'left-to-right)
+      (forward-symbol n)
+    (backward-symbol n)))
+
+(defun left-symbol (&optional n)
+  "Move point N symbols to the left (to the right if N is negative).
+for the definition of a symbol, see `symbol-mode'.
+
+Depending on the bidirectional context, this may move either backward
+or forward in the buffer.  This is in contrast with \\[forward-symbol]
+and \\[backward-symbol], which see."
+  (interactive "^p")
+  (if (eq (current-bidi-paragraph-direction) 'left-to-right)
+      (backward-symbol n)
+    (forward-symbol n)))
+
+(defun transpose-symbols (arg)
+  "Interchange symbols around point, leaving point at the end of them.
+for the definition of a symbol, see `symbol-mode'.
+With prefix argument ARG, the symbol before or around point is taken and
+dragged forward past ARG or other symbols (backwards if ARG is negative).
+If ARG is unspecified, the symbols around or after point and around or
+after mark are interchanged."
+  (interactive "*p")
+  (transpose-subr 'forward-symbol arg))
+
+(defun downcase-symbol (arg)
+  "Convert to lower case from point to the end of a symbol, moving over.
+For the definition of a symbol, see `symbol-mode'.
+With a positive prefix argument ARG, downcase that many symbols forward.
+Note that calling this function in the middle of a symbol doesn't
+affect it.  If ARG is negative, previous symbols, including the current
+one are converted, but the point won't move."
+  (interactive "p")
+  (let ((start (point)))
+    (if (minusp arg)
+        (progn
+          (forward-symbol arg)
+          (downcase-region start (point))
+          (goto-char start))
+      (downcase-region (point) (forward-symbol arg)))))
+
+
+(defun upcase-symbol (arg)
+  "Convert to upper case from point to the end of a symbol, moving over.
+For the definition of a symbol, see `symbol-mode'.
+With a positive prefix argument ARG, upcase that many symbols forward.
+Note that calling this function in the middle of a symbol doesn't
+affect it.  If ARG is negative, previous symbols, including the current
+one are converted, but the point won't move."
+  (interactive "p")
+  (let ((start (point)))
+    (if (minusp arg)
+        (progn
+          (forward-symbol arg)
+          (upcase-region start (point))
+          (goto-char start))
+      (upcase-region (point) (forward-symbol arg)))))
+
+(defun capitalize-symbol (arg)
+  "Capitalize from point to the end of symbol, moving over.
+For the definition of a symbol, see `symbol-mode'.
+With a positive prefix arugment ARG, capitalize that many symbols forward.
+Note that calling this function in the middle of a symbol doesn't
+affect it.  If ARG is negative, previous symbols, including the current
+one are converted, but the point won't move."
+  (interactive "p")
+  (let ((start (point)))
+    (if (minusp arg)
+        (progn
+          (forward-symbol arg)
+          (capitalize-region start (point))
+          (goto-char start))
+      (capitalize-region (point) (forward-symbol arg)))))
+
+(defvar-keymap symbol-mode-map
+  :doc "Keymap used for `symbol-mode'."
+  "<remap> <forward-word>" #'forward-symbol
+  "<remap> <backward-word>" #'backward-symbol
+  "<remap> <kill-word>" #'kill-symbol
+  "<remap> <mark-word>" #'mark-symbol
+  "<remap> <backward-kill-word>" #'backward-kill-symbol
+  "<remap> <left-word>" #'left-symbol
+  "<remap> <right-word>" #'right-symbol
+  "<remap> <transpose-words>" #'transpose-symbols
+  "<remap> <downcase-word>" #'downcase-symbol
+  "<remap> <upcase-word>" #'upcase-symbol
+  "<remap> <capitalize-word>" #'capitalize-symbol)
+
+;;;###autoload
+(define-minor-mode symbol-mode
+  "Replace word-related commands with symbols-related ones.
+A symbol is a word-like construct composed of a combination of words and
+signs, with no separating space."
+  :lighter " λ")
+
+(provide 'symbol)
+
+;;; symbol.el ends here
diff --git a/test/lisp/progmodes/symbol-tests.el b/test/lisp/progmodes/symbol-tests.el
new file mode 100644
index 00000000000..da334fdb792
--- /dev/null
+++ b/test/lisp/progmodes/symbol-tests.el
@@ -0,0 +1,84 @@
+;;; symbol-tests.el --- Testing the symbol-related commands -*- lexical-bint -*-
+
+;; Copyright (C) 2025 Free Software Foundation, Inc.
+
+;; Author: Gabriel Santos <[email-masked]>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file is used for testing the functionality of the
+;; `symbol' package.  It's pretty close to the regular word commands,
+;; but it's always nice ot have tests.
+
+;;; Code:
+
+(require 'ert)
+(require 'symbol)
+
+(defconst symbol-tests-strings
+  '("foo^" "foo-bar^" "foo_bar^" "foo/bar^" "孙/武^"))
+
+(ert-deftest symbol-tests ()
+  "Test the `symbol-mode' rules."
+  (with-temp-buffer
+    (dolist (str symbol-tests-strings)
+      (erase-buffer)
+      (insert str)
+      (goto-char (point-min))
+      (while (search-forward "^" nil t)
+        (replace-match ""))
+      (goto-char (point-min))
+      (while (not (eobp))
+        (forward-symbol 1)
+        (insert "^"))
+      (should (equal (buffer-string) str)))))
+
+(ert-deftest symbol-tests2 ()
+  "Test that motion in `symbol-mode' stops at the right places."
+
+  (let* ((line "foo foo-bar foo_bar foo/bar 365-symbol")
+         (fwrd "*  *       *       *       *          *")
+         (bkwd "*   *       *       *       *         *"))
+
+    (with-temp-buffer
+      (symbol-mode 1)
+      (insert line)
+
+      ;; Test forward motion.
+
+      (goto-char (point-min))
+      (let ((stops (make-string (length fwrd) ?\ )))
+        (while (progn
+                 (aset stops (1- (point)) ?\*)
+                 (not (eobp)))
+          (forward-symbol 1))
+        (should (equal stops fwrd)))
+
+      ;; Test backwards motion.
+
+      (goto-char (point-max))
+      (let ((stops (make-string (length bkwd) ?\ )))
+        (while (progn
+                 (aset stops (1- (point)) ?\*)
+                 (not (bobp)))
+          (backward-symbol 1))
+        (should (equal stops bkwd))))))
+
+
+(provide 'symbol-tests)
+;;; symbol-tests.el ends here
-- 
2.49.0