summaryrefslogtreecommitdiff
path: root/modules/prog-shell.el
diff options
context:
space:
mode:
Diffstat (limited to 'modules/prog-shell.el')
-rw-r--r--modules/prog-shell.el165
1 files changed, 161 insertions, 4 deletions
diff --git a/modules/prog-shell.el b/modules/prog-shell.el
index e63387cf..e9d12839 100644
--- a/modules/prog-shell.el
+++ b/modules/prog-shell.el
@@ -2,14 +2,171 @@
;; author Craig Jennings <c@cjennings.net>
;;; Commentary:
-;; Open any *.sh buffer and sh-mode loads with Flycheck attached, so syntax errors appear immediately.
-;; Re-save or invoke C-c ! l to refresh diagnostics while you iterate on scripts.
+;; Modern shell scripting environment with LSP, tree-sitter, linting, and formatting.
+;;
+;; Installation:
+;; sudo pacman -S shellcheck shfmt # Linter and formatter
+;; npm install -g bash-language-server
+;;
+;; Features:
+;; - LSP: Intelligent completion, hover docs, jump to definition
+;; - ShellCheck: Industry-standard linting (catches common bugs)
+;; - shfmt: Google's shell formatter (consistent style)
+;; - Tree-sitter: Better syntax highlighting via bash-ts-mode
+;; - Auto-executable: Scripts with shebangs auto-get execute permission
+;;
+;; Workflow:
+;; 1. Open .sh file → LSP auto-starts, ShellCheck runs
+;; 2. <f6> → Format with shfmt
+;; 3. C-c ! l → Show all ShellCheck diagnostics
+;; 4. Save → Auto-set executable bit if script has shebang
;;; Code:
+(defvar bash-ts-mode-map)
+(defvar sh-mode-map)
+
+;; Forward declarations for LSP
+(declare-function lsp-deferred "lsp-mode")
+(defvar lsp-bash-explainshell-endpoint)
+(defvar lsp-bash-highlight-parsing-errors)
+
+;; Forward declarations for sh-script
+(defvar sh-learn-basic-offset)
+
+;; Forward declarations for flycheck
+(defvar flycheck-shellcheck-follow-sources)
+(defvar flycheck-shellcheck-excluded-warnings)
+(defvar flycheck-checkers)
+
+;; Forward declarations for system utilities
+(declare-function cj/disabled "system-defaults")
+
+(defvar bash-language-server-path "bash-language-server"
+ "Path to bash-language-server executable.
+Install with: npm install -g bash-language-server")
+
+(defvar shfmt-path "shfmt"
+ "Path to shfmt executable.
+Install with: sudo pacman -S shfmt")
+
+(defvar shellcheck-path "shellcheck"
+ "Path to shellcheck executable.
+Install with: sudo pacman -S shellcheck")
+
+;; ------------------------------- Shell Script Setup ------------------------------
+;; preferences for shell scripting
+
+(defun cj/shell-script-setup ()
+ "Settings for shell script editing (bash, sh, zsh)."
+ (setq-local indent-tabs-mode nil) ;; use spaces, not tabs
+ (setq-local sh-basic-offset 2) ;; 2 spaces (common shell convention)
+ (setq-local tab-width 2) ;; tab displays as 2 spaces
+ (setq-local fill-column 80) ;; wrap at 80 columns
+ (electric-pair-mode t) ;; automatic quote/bracket pairing
+
+ ;; Enable LSP if available
+ (when (and (fboundp 'lsp-deferred)
+ (executable-find bash-language-server-path))
+ (lsp-deferred)))
+
+(defun cj/shell-run-shellcheck ()
+ "Run shellcheck on the current shell script."
+ (interactive)
+ (if (executable-find shellcheck-path)
+ (if buffer-file-name
+ (compile (format "%s %s" shellcheck-path (shell-quote-argument buffer-file-name)))
+ (message "No file associated with this buffer"))
+ (message "shellcheck not found. Install with: sudo pacman -S shellcheck")))
+
+(defun cj/shell-mode-keybindings ()
+ "Set up keybindings for shell script editing.
+Overrides default prog-mode keybindings with shell-specific commands."
+ ;; S-f5: Run shellcheck (static analysis)
+ (local-set-key (kbd "S-<f5>") #'cj/shell-run-shellcheck)
+
+ ;; S-f6: Disabled (shell scripts don't have interactive debugging like gdb/pdb)
+ (local-set-key (kbd "S-<f6>") #'cj/disabled))
+
+;; Apply to both legacy sh-mode and modern bash-ts-mode
+(add-hook 'sh-mode-hook 'cj/shell-script-setup)
+(add-hook 'bash-ts-mode-hook 'cj/shell-script-setup)
+(add-hook 'sh-mode-hook 'cj/shell-mode-keybindings)
+(add-hook 'bash-ts-mode-hook 'cj/shell-mode-keybindings)
+
+;; -------------------------------- Shell Scripts ----------------------------------
+;; built-in shell script mode configuration
+
(use-package sh-script
- :defer .5
- :hook (sh-mode . flycheck-mode))
+ :ensure nil ;; built-in
+ :mode (("\\.sh\\'" . bash-ts-mode) ;; .sh files use bash-ts-mode
+ ("\\.bash\\'" . bash-ts-mode) ;; .bash files
+ ("\\.zsh\\'" . sh-mode) ;; zsh doesn't have ts-mode yet
+ ("/PKGBUILD\\'" . bash-ts-mode)) ;; Arch Linux PKGBUILDs
+ :config
+ ;; Set default shell type
+ (setq sh-shell-file "/bin/bash")
+
+ ;; Improve shell script detection
+ (setq sh-learn-basic-offset t))
+
+;; -------------------------------- LSP for Shell ----------------------------------
+;; Shell script LSP configuration using bash-language-server
+;; Core LSP setup is in prog-general.el
+
+(use-package lsp-mode
+ :hook ((sh-mode bash-ts-mode) . lsp-deferred)
+ :config
+ ;; Configure bash-language-server
+ (setq lsp-bash-explainshell-endpoint nil) ;; Disable external API calls
+ (setq lsp-bash-highlight-parsing-errors t))
+
+;; --------------------------------- ShellCheck ------------------------------------
+;; Industry-standard shell script linter
+
+(use-package flycheck
+ :if (executable-find shellcheck-path)
+ :hook ((sh-mode bash-ts-mode) . flycheck-mode)
+ :config
+ ;; Prefer ShellCheck over basic sh linter
+ (setq flycheck-shellcheck-follow-sources t)
+ (setq flycheck-shellcheck-excluded-warnings '("SC2086")) ;; Customize as needed
+
+ ;; Use ShellCheck for shell scripts
+ (add-to-list 'flycheck-checkers 'sh-shellcheck))
+
+;; -------------------------------- Formatting -------------------------------------
+;; Format shell scripts with shfmt
+
+(use-package shfmt
+ :if (executable-find shfmt-path)
+ :hook ((sh-mode bash-ts-mode) . shfmt-on-save-mode)
+ :bind ((:map sh-mode-map
+ ("<f6>" . shfmt-buffer)
+ ("C-; f" . shfmt-buffer))
+ (:map bash-ts-mode-map
+ ("<f6>" . shfmt-buffer)
+ ("C-; f" . shfmt-buffer)))
+ :custom
+ (shfmt-arguments '("-i" "2" ;; indent with 2 spaces
+ "-ci" ;; indent switch cases
+ "-bn"))) ;; binary ops like && and | may start a line
+
+;; ---------------------------- Auto-Executable Scripts ----------------------------
+;; Automatically set execute permission on shell scripts with shebangs
+
+(defun cj/make-script-executable ()
+ "Make the current file executable if it has a shebang."
+ (when (and buffer-file-name
+ (not (file-executable-p buffer-file-name))
+ (save-excursion
+ (goto-char (point-min))
+ (looking-at "^#!")))
+ (set-file-modes buffer-file-name
+ (logior (file-modes buffer-file-name) #o111))
+ (message "Made %s executable" (file-name-nondirectory buffer-file-name))))
+
+(add-hook 'after-save-hook 'cj/make-script-executable)
(provide 'prog-shell)
;;; prog-shell.el ends here