summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/NOTES.org31
-rw-r--r--modules/flycheck-config.el54
-rw-r--r--modules/weather-config.el2
-rwxr-xr-xscripts/languagetool-flycheck82
-rw-r--r--todo.org23
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()
diff --git a/todo.org b/todo.org
index 3641c912..126dc62f 100644
--- a/todo.org
+++ b/todo.org
@@ -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