diff options
| -rw-r--r-- | docs/NOTES.org | 31 | ||||
| -rw-r--r-- | modules/flycheck-config.el | 54 | ||||
| -rw-r--r-- | modules/weather-config.el | 2 | ||||
| -rwxr-xr-x | scripts/languagetool-flycheck | 82 | ||||
| -rw-r--r-- | todo.org | 23 |
5 files changed, 162 insertions, 30 deletions
diff --git a/docs/NOTES.org b/docs/NOTES.org index 7de74826..da8f573e 100644 --- a/docs/NOTES.org +++ b/docs/NOTES.org @@ -152,6 +152,36 @@ emacs --batch file.el --eval '(check-parens)' && echo "✓" - Prevents committing broken code - Example in chime.el repository: .git/hooks/pre-commit +*** 5. Test Emacs Launch After Non-Trivial Changes +**CRITICAL: Always test that Emacs launches without errors after making changes** + +After completing any non-trivial code changes (new modules, integrations, etc.): + +#+BEGIN_SRC bash +# 1. Run unit tests (if applicable) +make test + +# 2. Test that Emacs launches without backtrace +emacs --eval "(kill-emacs)" +#+END_SRC + +This catches: +- Syntax errors in :command specifications +- Missing dependencies at load time +- Invalid use-package configurations +- Broken requires/provides + +**When to do this:** +- ✅ After adding new checker definitions (flycheck, flymake) +- ✅ After creating new modules +- ✅ After modifying init.el or early-init.el +- ✅ Before committing changes +- ✅ After running all unit tests + +**Example lesson:** The LanguageTool integration used =(eval (expand-file-name ...))= in +a =:command= specification, which caused a backtrace on startup. Testing Emacs launch +would have caught this immediately instead of discovering it on next restart. + ** For Human Developers *** 1. Use Structural Editing Modes @@ -216,6 +246,7 @@ Deeply nested code: | Writing | paredit/smartparens | Prevent errors | | Editing | rainbow-delimiters | Visual verification | | Testing | check-parens | Quick syntax check | +| Testing | emacs --eval "(kill-emacs)" | Verify Emacs launches | | CI/CD | pre-commit hooks | Prevent bad commits | | Review | byte-compile-file | Comprehensive check | diff --git a/modules/flycheck-config.el b/modules/flycheck-config.el index ea19f08f..e2e8abe9 100644 --- a/modules/flycheck-config.el +++ b/modules/flycheck-config.el @@ -6,30 +6,30 @@ ;; This file configures Flycheck for on-demand syntax and grammar checking. ;; - Flycheck starts automatically only in sh-mode and emacs-lisp-mode -;; - This binds a custom helper (=cj/flycheck-list-errors=) to “C-; ?” +;; - This binds a custom helper (=cj/flycheck-list-errors=) to "C-; ?" ;; for popping up Flycheck's error list in another window. -;; - It also customizes Checkdoc to suppress only the “sentence-end-double-space” -;; and “warn-escape” warnings. +;; - It also customizes Checkdoc to suppress only the "sentence-end-double-space" +;; and "warn-escape" warnings. -;; - It registers a Proselint checker for prose files -;; (text-mode, markdown-mode, gfm-mode). +;; - It registers LanguageTool for comprehensive grammar checking of prose files +;; (text-mode, markdown-mode, gfm-mode, org-mode). -;; Note: I do use proselint quite a bit in emails and org-mode files. However, some -;; org-files can be large and running proselint on them will slow Emacs to a crawl. -;; Therefore, hitting "C-; ?" also runs cj/flycheck-prose-on-demand if in an org buffer. +;; Note: Grammar checking is on-demand only to avoid performance issues. +;; Hitting "C-; ?" runs cj/flycheck-prose-on-demand if in an org buffer. -;; ;; The cj/flycheck-prose-on-demand function: ;; - Turns on flycheck for the local buffer -;; - ensures proselint is added -;; - triggers an immediate check -;; -;; Since this is called within cj/flycheck-list-errors, flycheck's error list will still -;; display and the focus transferred to that buffer. +;; - Enables LanguageTool checker +;; - Triggers an immediate check +;; - Displays errors in the *Flycheck errors* buffer -;; OS Dependencies: -;; proselint (in the Arch AUR) +;; Installation: +;; On Arch Linux: +;; sudo pacman -S languagetool +;; +;; The wrapper script at scripts/languagetool-flycheck formats LanguageTool's +;; JSON output into flycheck-compatible format. It requires Python 3. ;;; Code: @@ -62,20 +62,20 @@ ;; use the load-path of the currently running Emacs instance (setq flycheck-emacs-lisp-load-path 'inherit) - ;; Define the prose checker (installed separately via OS). - (flycheck-define-checker proselint - "A linter for prose." - :command ("proselint" source-inplace) + ;; Define LanguageTool checker for comprehensive grammar checking + (flycheck-define-checker languagetool + "A grammar checker using LanguageTool. +Uses a wrapper script to format output for flycheck." + :command ("~/.emacs.d/scripts/languagetool-flycheck" + source-inplace) :error-patterns ((warning line-start (file-name) ":" line ":" column ": " - (id (one-or-more (not (any " ")))) (message) line-end)) :modes (text-mode markdown-mode gfm-mode org-mode)) - (add-to-list 'flycheck-checkers 'proselint) + (add-to-list 'flycheck-checkers 'languagetool) (defun cj/flycheck-list-errors () "Display flycheck's error list and switch to its buffer. - Runs flycheck-prose-on-demand if in an org-buffer." (interactive) (when (derived-mode-p 'org-mode) @@ -85,12 +85,14 @@ Runs flycheck-prose-on-demand if in an org-buffer." (switch-to-buffer-other-window "*Flycheck errors*")) (defun cj/flycheck-prose-on-demand () - "Enable Flycheck+Proselint in this buffer, run it, and show errors." + "Enable Flycheck with LanguageTool in this buffer, run it, and show errors." (interactive) ;; turn on Flycheck locally (flycheck-mode 1) - ;; ensure proselint is valid for org/text - (flycheck-add-mode 'proselint major-mode) + ;; ensure LanguageTool is valid for current mode + (flycheck-add-mode 'languagetool major-mode) + ;; select LanguageTool as the checker + (setq-local flycheck-checker 'languagetool) ;; trigger immediate check (flycheck-buffer))) diff --git a/modules/weather-config.el b/modules/weather-config.el index 55eddf16..3a30aa17 100644 --- a/modules/weather-config.el +++ b/modules/weather-config.el @@ -14,7 +14,7 @@ (add-to-list 'load-path "/home/cjennings/code/wttrin") ;; Set debug flag BEFORE loading wttrin (checked at load time) -(setq wttrin-debug t) +(setq wttrin-debug nil) (use-package wttrin ;; Uncomment the next line to use vc-install instead of local directory: diff --git a/scripts/languagetool-flycheck b/scripts/languagetool-flycheck new file mode 100755 index 00000000..ecbc900f --- /dev/null +++ b/scripts/languagetool-flycheck @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +""" +Wrapper for LanguageTool to produce flycheck-compatible output. +Output format: filename:line:column: message +""" + +import json +import sys +import subprocess + +def main(): + if len(sys.argv) < 2: + print("Usage: languagetool-flycheck FILE", file=sys.stderr) + sys.exit(1) + + filename = sys.argv[1] + + # Run languagetool with JSON output + try: + result = subprocess.run( + ['languagetool', '-l', 'en-US', '--json', filename], + capture_output=True, + text=True, + timeout=30 + ) + except subprocess.TimeoutExpired: + print(f"{filename}:1:1: LanguageTool timeout", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"{filename}:1:1: LanguageTool error: {e}", file=sys.stderr) + sys.exit(1) + + # Parse JSON output + try: + # Find the JSON in the output (skip warning lines) + json_output = None + for line in result.stdout.split('\n'): + if line.startswith('{'): + json_output = line + break + + if not json_output: + sys.exit(0) # No errors found + + data = json.loads(json_output) + + # Read file to calculate line numbers from character offsets + with open(filename, 'r', encoding='utf-8') as f: + content = f.read() + + # Convert matches to flycheck format + for match in data.get('matches', []): + offset = match['offset'] + length = match['length'] + message = match['message'] + rule_id = match['rule']['id'] + + # Calculate line and column from offset + line = content[:offset].count('\n') + 1 + line_start = content.rfind('\n', 0, offset) + 1 + column = offset - line_start + 1 + + # Get first suggestion if available + suggestions = match.get('replacements', []) + if suggestions: + suggestion = suggestions[0]['value'] + message = f"{rule_id}: {message} Suggestion: {suggestion}" + else: + message = f"{rule_id}: {message}" + + # Output in flycheck format + print(f"{filename}:{line}:{column}: {message}") + + except json.JSONDecodeError as e: + print(f"{filename}:1:1: Failed to parse LanguageTool JSON: {e}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"{filename}:1:1: Error processing LanguageTool output: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == '__main__': + main() @@ -204,9 +204,26 @@ Use M-x profiler-start before Method 3 debug-profiling.el is built. 15-20 seconds every time capturing a task (12+ times/day). Major daily bottleneck - minutes lost waiting, plus context switching cost. -** TODO [#C] Fix grammar checker performance (currently disabled) - -Currently disabled because it breaks flow when writing. +** DONE [#C] Fix grammar checker performance (currently disabled) +CLOSED: [2025-11-04 Mon] + +✅ **Installed and configured LanguageTool for comprehensive grammar checking** + +Replaced disabled grammar checker with on-demand LanguageTool integration: +- Installed LanguageTool 6.6-2 from Arch repos (222MB) +- Created wrapper script: scripts/languagetool-flycheck (Python 3) +- Integrated with flycheck for on-demand checking via C-; ? +- Removed proselint (redundant - LanguageTool catches more) +- No performance impact: only runs when explicitly invoked +- Installation instructions added to modules/flycheck-config.el commentary + +LanguageTool catches: +- Real grammar errors (subject-verb agreement, tense, etc.) +- Missing punctuation (commas, periods) +- Common mistakes (could of → could have) +- Style issues (redundant phrases, wordiness) + +Workflow: Open org/text/markdown file → press C-; ? → see errors in *Flycheck errors* buffer ** TODO [#D] Fix EMMS keybinding inconsistency with other buffers |
