Repository navigation
Report messages once when running a command for multiple cursors - #2329
Merged
theangelperalta merged 4 commits intoNov 11, 2026
Merged
Conversation
process-each-cursors runs a command's body once per fake cursor and then for the real cursor, so any `message` in the body was shown and logged once per cursor. lem-project#2263 guarded the call in mark-set, but other per-cursor commands such as yank-pop still repeat. Bind *inhibit-message* during the fake-cursor passes so only the real cursor's pass reports, and drop the now-redundant guard in mark-set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
✅ Code Contractor Validation: PASSED 📌 Result for commit 📋 Contract Configuration: contract (Source: Repository)version: 2
trigger:
paths:
- "extensions/**"
- "frontends/**/*.lisp"
- "src/**"
- "tests/**"
- "contrib/**"
- "**/*.asd"
head_branches:
exclude:
- 'revert-*'
validation:
limits:
max_total_changed_lines: 400
max_delete_ratio: 0.5
max_files_changed: 10
severity: warning
ai:
system_prompt: |
You are a senior Common Lisp engineer reviewing code for Lem editor.
Lem is a text editor with multiple frontends (ncurses, SDL2, webview).
Focus on maintainability, consistency with existing code, and Lem-specific conventions.
rules:
# === File Structure ===
- name: defpackage_rule
prompt: |
First form must be `defpackage` or `uiop:define-package`.
Package name should match filename (e.g., `foo.lisp` → `:lem-ext/foo` or `:lem-foo`).
Extensions must use `lem-` prefix (e.g., `:lem-python-mode`).
- name: file_structure_rule
prompt: |
File organization (top to bottom):
1. defpackage
2. defvar/defparameter declarations
3. Key bindings (define-key, define-keys)
4. Class/struct definitions
5. Functions and commands
Only flag violations when elements appear OUT OF ORDER (e.g., key bindings AFTER functions, or defvar AFTER functions).
Key bindings appearing BEFORE functions is correct and expected.
Exception: In major-mode files (e.g., *-mode.lisp), flexible ordering is acceptable when it follows established patterns from similar modes (e.g., clojure-mode, python-mode).
# === Style ===
- name: loop_keywords_rule
prompt: |
Loop keywords must use colons: `(loop :for x :in list :do ...)`
NOT: `(loop for x in list do ...)`
- name: naming_conventions_rule
prompt: |
Naming conventions:
- Functions/variables: kebab-case (e.g., `find-buffer`)
- Special variables: *earmuffs* (e.g., `*global-keymap*`)
- Constants: +plus-signs+ (e.g., `+default-tab-size+`)
- Predicates: -p suffix for functions (e.g., `buffer-modified-p`)
- Do NOT use -p suffix for user-configurable variables
# === Documentation ===
- name: docstring_rule
prompt: |
Required docstrings for:
- Exported functions, methods, classes
- `define-command` (explain what the command does)
- Generic functions (`:documentation` option)
Important functions should explain "why", not just "what".
severity: warning
# === Lem-Specific ===
- name: internal_symbol_rule
prompt: |
Use exported symbols from `lem` or `lem-core` package.
Avoid `lem::internal-symbol` access.
If internal access is necessary, document why.
- name: error_handling_rule
prompt: |
- `error`: Internal/programming errors
- `editor-error`: User-facing errors (displayed in echo area)
Always use `editor-error` for messages shown to users.
- name: frontend_interface_rule
prompt: |
Frontend-specific code must use `lem-if:*` protocol.
Do not call frontend implementation directly from core.
severity: warning
# === Functional Style ===
- name: functional_style_rule
prompt: |
Prefer explicit function arguments over dynamic variables.
Avoid using `defvar` for state passed between functions.
Exception: Well-documented cases like `*current-buffer*`, constants, and configuration variables (not meant to be modified at runtime).
- name: dynamic_symbol_call_rule
prompt: |
Avoid `uiop:symbol-call`. Rethink architecture instead.
Exception: Acceptable in extensions when following patterns established in other major-mode extensions (e.g., for optional dependency loading or plugin systems).
If used, document the reason.
# === Libraries ===
- name: alexandria_usage_rule
prompt: |
Alexandria utilities allowed: `if-let`, `when-let`, `with-gensyms`, etc.
Avoid: `alexandria:curry` (use explicit lambdas)
Avoid: `alexandria-2:*` functions not yet used in codebase
# === Macros ===
- name: macro_style_rule
prompt: |
Keep macros small. For complex logic, use `call-with-*` pattern:
```lisp
(defmacro with-foo (() &body body)
`(call-with-foo (lambda () ,@body)))
```
Prefer `list` over backquote outside macros.
Exception: In major-mode files, macro patterns that are standard in other major-modes (e.g., clojure-mode, python-mode) are acceptable.📚 About Code ContractorDeclarative Code Standards That Learn and Improve Define domain-specific validation rules in YAML. Want this for your repo? |
The test doesn't write to the kill ring, so it doesn't need its own binding of lem-core::*killring*. cursor-mark and buffer-cursors are exported from lem-core. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
/rerun |
process-each-cursors already rebinds the buffer point to each fake cursor, so message can check the current point instead of a separate special variable. Add fake-cursor-p and use it in message and when-real-cursor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
/rerun |
Checking the current point in message had several problems: call-with-buffer-point changes the buffer point for every thread, so messages from other threads could be dropped; a message sent from another buffer was still repeated; and a message that only a fake cursor sent was lost. process-each-cursors now binds *cursor-messages* and marks fake-cursor passes with a dynamic binding. Messages from fake passes are deferred and shown after the real pass, unless the real cursor already showed the same one. message-buffer goes through the same path. Also make C-u mark-set move each fake cursor to its own mark instead of popping the shared mark ring once per cursor, and export fake-cursor-p.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #2263.
Problem
process-each-cursorsruns a command's body once per fake cursor and then once for the real cursor. Anymessagein that body is shown, and logged to*Messages*, once per cursor. #2263 fixed this formark-setwith a call-site guard. Other per-cursor commands still repeat, for exampleyank-pop/yank-pop-nextwhen the previous command wasn't a yank. Every new command would need the same guard.Change
fake-cursor-p(insrc/cursors.lisp).process-each-cursorsalready rebinds the buffer point to each fake cursor, somessageandmessage-without-logcheck(fake-cursor-p (current-point))and do nothing during the fake-cursor passes. The real cursor's pass, which always runs last, reports as before.when-real-cursornow usesfake-cursor-ptoo.when-real-cursorcall inmark-set, which is now redundant. The macro stays exported, so anything already using it keeps working.This changes
messagefor everyone: a message printed while a command runs for a fake cursor is now dropped, including messages from extensions. Errors are unchanged:editor-errorstill propagates from any cursor.Correction to my review on #2263
In that review I said the per-cursor
messagecaused an%always-same-bufferassertion crash. That was an artifact of my headless setup: it calledset-window-bufferdirectly, which left the window's view point in the old buffer. It fails the same way with zero fake cursors. With the buffer switched in properly (switch-to-bufferplus a timer manager), neithermark-setnoryank-popcrashes, before or after #2263. The real symptom was the duplicate messages.Tests
multiple-cursors-message-onceintests/cursors.lisp. It runsmark-setandyank-popwith two fake cursors and checks that every cursor gets its mark and that each message is logged exactly once. Theyank-popcase fails without this change.lem-tests: all 47 pass.🤖 Generated with Claude Code