From a6ba5e74a3e53f9adcc93a71bebaa63c5b4f1953 Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Wed, 9 Oct 2024 07:32:06 -0700 Subject: [PATCH] A bunch of changes --- elisp/latex-help.el | 273 ++++++++++++++++++++++++++++++++++++++++++++ init.el | 93 +++++++++------ 2 files changed, 332 insertions(+), 34 deletions(-) create mode 100644 elisp/latex-help.el diff --git a/elisp/latex-help.el b/elisp/latex-help.el new file mode 100644 index 0000000..ecbcaa5 --- /dev/null +++ b/elisp/latex-help.el @@ -0,0 +1,273 @@ +;;; latex-help.el --- Lookup LaTeX symbols -*- lexical-binding: t -*- + +;;; Commentary: +;; This is inspired by an old package (originally from the 90s!!) called +;; ltx-help.el. This package used to be called latex-help.el too, but it seems +;; to have had its name changed sometime around 2010. This package aims for +;; similar functionality, but using more up to date and convention-conforming +;; Elisp. For example, the original package still assumes that you may not have +;; `add-hook' or `buffer-substring-no-properties'. Only very old versions of +;; Emacs are missing these, so almost everyone has them nowadays. + +;;; Code: +(require 'info) + +(defcustom latex-help-info-manual "latex2e" + "The name of the info manual to use when looking up latex commands." + :group 'latex-help + :type '(choice + (string :tag "English" "latex2e") + (string :tag "French" "latex2e-fr") + (string :tag "Spanish" "latex2e-es"))) + +(defcustom latex-help-buffer-name "*latex-help*" + "The name of the info buffer to use when showing LaTeX documentation." + :group 'latex-help + :type 'string) + +(defun latex-help--run-index-search (regexp) + "Search the LaTeX info pages index for REGEXP. +This returns a list of cache entries suitable for use in +`latex-help--commands-cache'." + (with-temp-buffer + (Info-mode) + (Info-find-node latex-help-info-manual "Index" nil t) + (let ((found)) + (while (re-search-forward regexp nil t) + (let ((match (match-string-no-properties 1)) + (node (match-string-no-properties 2))) + (if (equal (caar found) match) + (push (cons node (pos-bol)) (cdar found)) + (push (list match (cons node (pos-bol))) found)))) + found))) + +(defvar latex-help--commands-cache nil + "Cahce of discovered of LaTeX commands. +These do NOT have a leading '\\'.") + +(defun latex-help--discover-commands () + "Discover LaTeX commands. +This is done by parsing the index for `latex-help-info-manual'." + (let ((found (latex-help--run-index-search + (rx (and bol "* \\" + (group (or + "," + (+ (not (any " " "{" ","))))) + (*? any) ":" (+ " ") + (group (+? any)) "."))))) + (push (list "(SPACE)" "\\(SPACE)") found) + (when-let (entry (assoc "(...\\)" found)) + (setq found (assoc-delete-all "(...\\)" found)) + (push (cons "(" (cdr entry)) found) + (push (cons ")" (cdr entry)) found)) + (when-let (entry (assoc "[...\\]" found)) + (setq found (assoc-delete-all "[...\\]" found)) + (push (cons "[" (cdr entry)) found) + (push (cons "]" (cdr entry)) found)) + found)) + +(defvar latex-help--package-cache nil + "Cache of discovered LaTeX packages.") + +(defun latex-help--discover-packages () + "Discover LaTeX packages. +This is done by parsing the index for `latex-help-info-manual'." + (latex-help--run-index-search (rx (and bol "* package, " + (group (+? any)) + (any " " ":") + (+? any) (+ " ") + (group (+? any)) + ".")))) + +(defvar latex-help--environment-cache nil + "Cache of discovered LaTeX environments.") + +(defun latex-help--discover-environments () + "Discover LaTeX environments. +This is done by parsing the index for `latex-help-info-manual'." + (latex-help--run-index-search (rx (and bol "* environment, " + (group (+? any)) + (any " " ":" "-") + (+? any) (+ " ") + (group (+? any)) + ".")))) + +(defvar latex-help--class-cache nil + "Cache of discovered LaTeX document classes.") + +(defun latex-help--discover-classes () + "Discover LaTeX document classes. +This is done by parsing the index for `latex-help-info-manual'." + (latex-help--run-index-search (rx (and bol "* " + (group (+ (not (any "," " ")))) + " class:" (+ " ") + (group (+ (not "."))))))) + +(defun latex-help--goto-entry (entry) + "Open the info page for ENTRY, a cache entry." + (let ((buffer (get-buffer-create latex-help-buffer-name))) + (with-current-buffer buffer + (unless (derived-mode-p 'Info-mode) + (Info-mode)) + (Info-find-node latex-help-info-manual "Index" nil t) + (goto-char (cdr entry)) + (Info-follow-nearest-node)) + (pop-to-buffer buffer))) + +(defvar latex-help--caches-initialized-p nil + "Non-nil if the latex-help caches have been initialized.") + +(defun latex-help--get-cache-for-type (type) + "Lookup the cache for TYPE. +If the caches are not yet initialized, do that first." + (unless latex-help--caches-initialized-p + (setq latex-help--commands-cache (latex-help--discover-commands) + latex-help--package-cache (latex-help--discover-packages) + latex-help--environment-cache (latex-help--discover-environments) + latex-help--class-cache (latex-help--discover-classes) + latex-help--caches-initialized-p t)) + (cond + ((eq type 'command) + latex-help--commands-cache) + ((eq type 'package) + latex-help--package-cache) + ((eq type 'environment) + latex-help--environment-cache) + ((eq type 'class) + latex-help--class-cache))) + +(defun latex-help--history nil + "History list for `latex-help--maybe-prompt-entry'.") + +(defun latex-help--maybe-prompt-entry (name type &optional default) + "Lookup and prompt the user for the node of NAME. +The lookup is performed in the correct cache for TYPE. If there is only one +node associated with NAME, return its entry. Otherwise, ask the user which node +they want to use. + +If DEFAULT is non-nil, use that instead of prompting. If it does not exist, +return nil." + (when-let (entries (alist-get name (latex-help--get-cache-for-type type) + nil nil 'equal)) + (cond + (default + (assoc default entries)) + ((length= entries 1) + (car entries)) + (t + (let ((resp (completing-read "Select Node: " (mapcar 'car entries) + nil t nil))) + (assoc resp entries)))))) + +(defun latex-help--get-thing-at-point () + "Return a cons of the LaTeX thing at point and its type (as a symbol). +If nothing is found, return nil. +The following types are known: + - command + - package + - environment + - class + +The following are some examples: + - \\textbf{Hello World} -> \\='(\"textbf\" . command) + - \\begin{math} (on \"math\") -> \\='(\"math\" . environment) + - \\begin{math} (on \"begin\") -> \\='(\"begin\" . command) + - \\usepackage{amsmath} (on \"amsmath\") -> \\='(\"amsmath\" . package) + - \\usepackage{amsmath} (on \"usepackage\") -> \\='(\"usepackage\" . command)" + (save-excursion + (let ((orig-point (point))) + (when (eq (char-after) ?\\) + (forward-char)) + (when (and (search-backward "\\" nil t) + (looking-at (rx "\\" + (group (+ (not (any " " "\n" "(" + "{" "[" "|" + "}" "]" ")"))))))) + (let ((cmd (match-string-no-properties 1))) + (if (> (match-end 1) orig-point) + (cons cmd 'command) + (goto-char orig-point) + (condition-case _ + (progn + (backward-up-list nil t t) + (when (looking-at (rx "{" (group (+ (not (any "}" "\n")))))) + (let ((thing (match-string-no-properties 1))) + (cond + ((equal cmd "usepackage") + (cons thing 'package)) + ((or (equal cmd "begin") + (equal cmd "end")) + (cons thing 'environment)) + ((equal cmd "documentclass") + (cons thing 'class)))))) + ;; just return nil + ((or user-error scan-error))))))))) + +(defun latex-help--prompt-for (type) + "Prompt for a command, environment, etc. from TYPE. +This returns the name of the thing that was prompted." + (let* ((cache (latex-help--get-cache-for-type type)) + (tap (latex-help--get-thing-at-point)) + (default (and (eq (cdr tap) type) (car tap)))) + (unless (assoc default cache) + (setq default nil)) + (completing-read (format "LaTeX %s%s: " + (capitalize (symbol-name type)) + (if default + (format " (default %s)" default) + "")) + (latex-help--get-cache-for-type type) + nil t nil 'latex-help--history + default))) + +(defun latex-help-command (name &optional node) + "Lookup the LaTeX command NAME. +Unless NODE is non-nil, if NAME is in more than one node, prompt the user for +which to use. If NODE is non-nil, use that instead." + (interactive (list (latex-help--prompt-for 'command))) + (when-let (entry (latex-help--maybe-prompt-entry name 'command node)) + (latex-help--goto-entry entry))) + +(defun latex-help-environment (name &optional node) + "Lookup the LaTeX environment NAME. +Unless NODE is non-nil, if NAME is in more than one node, prompt the user for +which to use. If NODE is non-nil, use that instead." + (interactive (list (latex-help--prompt-for 'environment))) + (when-let (entry (latex-help--maybe-prompt-entry name 'environment node)) + (latex-help--goto-entry entry))) + +(defun latex-help-package (name &optional node) + "Lookup the LaTeX package NAME. +Unless NODE is non-nil, if NAME is in more than one node, prompt the user for +which to use. If NODE is non-nil, use that instead." + (interactive (list (latex-help--prompt-for 'package))) + (when-let (entry (latex-help--maybe-prompt-entry name 'package node)) + (latex-help--goto-entry entry))) + +(defun latex-help-class (name &optional node) + "Lookup the LaTeX document class NAME. +Unless NODE is non-nil, if NAME is in more than one node, prompt the user for +which to use. If NODE is non-nil, use that instead." + (interactive (list (latex-help--prompt-for 'class))) + (when-let (entry (latex-help--maybe-prompt-entry name 'class node)) + (latex-help--goto-entry entry))) + +(defun latex-help-at-point () + "Try to lookup the LaTeX thing at point, whatever it may be. +This will try to look up the command, package, document class, or environment at +point. If that thing at point is valid, it will open an info buffer to the +documentation for that thing." + (interactive) + (if-let (thing (latex-help--get-thing-at-point)) + (if-let (entry (latex-help--maybe-prompt-entry (car thing) + (cdr thing))) + (latex-help--goto-entry entry) + (user-error "Unknown %s: \"%s\"" + (symbol-name (cdr thing)) + (if (eq (cdr thing) 'command) + (concat "\\" (car thing)) + (car thing)))) + (user-error "Nothing at point to look up"))) + +(provide 'latex-help) +;;; latex-help.el ends here diff --git a/init.el b/init.el index a06f2af..1fb2657 100644 --- a/init.el +++ b/init.el @@ -215,7 +215,8 @@ BUFFER can be a string or a buffer." ((stringp buffer) (not (string-prefix-p " " buffer))) ((bufferp buffer) - (my/buffer-visible-p (buffer-name buffer))) + (and (stringp (buffer-name buffer)) + (my/buffer-visible-p (buffer-name buffer)))) (t (signal 'wrong-type-argument `((or bufferp stringp) ,buffer))))) @@ -378,7 +379,8 @@ directory. Otherwise, run `find-file' on that file." :hook (undo-tree-visualizer-mode . my/-undo-tree-visualizer-mode-setup) :config (defun my/-undo-tree-visualizer-mode-setup () - (visual-line-mode -1)) + (visual-line-mode -1) + (setq truncate-lines t)) (global-undo-tree-mode)) ;; evil @@ -722,11 +724,21 @@ to `posframe-show' if the display is graphical." :init (defun my/flymake-show-diagnostic-at-point () (interactive) - (if-let ((pos (point)) - (diag (and flymake-mode - (get-char-property pos 'flymake-diagnostic))) - (message (flymake--diag-text diag))) - (my/floating-tooltip " *flymake-error-posframe*" message)))) + (when flymake-mode + (let* ((diag (get-char-property (point) 'flymake-diagnostic)) + (diag-msg (when diag + (apply 'concat + (mapcar + (lambda (msg) + (concat "•" msg "\n")) + (split-string (flymake--diag-text diag) + "\n"))))) + (jinx-msg (when (jinx--get-overlays (point) (1+ (point))) + "•misspelled word\n"))) + (unless (and (zerop (length diag-msg)) + (zerop (length jinx-msg))) + (my/floating-tooltip " *flymake-error-posframe*" + (concat diag-msg jinx-msg))))))) ;; flycheck (use-package flycheck @@ -740,12 +752,15 @@ to `posframe-show' if the display is graphical." (defun my/flycheck-show-diagnostic-at-point () (interactive) (if-let ((flycheck-mode) - (errors (flycheck-overlay-errors-at (point))) - (message (apply 'concat - (mapcar - (lambda (error) - (concat "•" (flycheck-error-message error) "\n")) - errors)))) + (message + (apply 'concat + (nconc (mapcar + (lambda (error) + (concat "•" (flycheck-error-message error) "\n")) + (flycheck-overlay-errors-at (point))) + (when (jinx--get-overlays (point) (1+ (point))) + '("•misspelled word\n"))))) + ((not (zerop (length message))))) (my/floating-tooltip " *flycheck-error-posframe*" (substring message 0 (1- (length message))))))) (use-package consult-flycheck @@ -1019,7 +1034,7 @@ When EXCLUDE-BRACES is non-nil, don't count the first and last brace of the entry as in the entry. That is, if the point is on the first { or last } of the entry, return nil." (save-excursion - (when (and exclude-braces (= ?\} (char-after))) + (when (and exclude-braces (eq ?\} (char-after))) (forward-char)) ;; go to top level and check if the character at point is { (let ((start-pos (point)) @@ -1030,7 +1045,7 @@ entry, return nil." (setq last-valid (point))) (error (and - (= ?\{ (char-after last-valid)) + (eq ?\{ (char-after last-valid)) (or (not exclude-braces) (not (= start-pos last-valid))))))))) (defvar my/bibtex-indent-width 4 @@ -1082,10 +1097,14 @@ otherwise, call `bibtex-find-text'." (evil-define-key 'normal bibtex-mode-map (kbd "TAB") 'my/bibtex-indent-or-find-text-and-insert) +;; Latex help (from elisp file) +(require 'latex-help) + ;; AUCTeX (use-package auctex :hook ((LaTeX-mode . turn-on-reftex) - (LaTeX-mode . LaTeX-math-mode)) + (LaTeX-mode . LaTeX-math-mode) + (LaTeX-mode . my/-setup-LaTeX-mode)) :init (add-to-list 'major-mode-remap-alist '(plain-tex-mode . plain-TeX-mode)) (add-to-list 'major-mode-remap-alist '(latex-mode . LaTeX-mode)) @@ -1095,6 +1114,8 @@ otherwise, call `bibtex-find-text'." (add-to-list 'major-mode-remap-alist '(doctex-mode . docTeX-mode)) (add-to-list 'auto-mode-alist '("/\\.latexmkrc\\'" . perl-mode)) :config + (defun my/-setup-LaTeX-mode () + (setq evil-lookup-func 'latex-help-at-point)) (setq TeX-auto-save t TeX-parse-self t reftex-plug-into-AUCTeX t) @@ -1419,7 +1440,7 @@ argument." (funcall-interactively #'sage-shell:run-sage (sage-shell:read-command))))) -;; fricas (because I like calculator) +;; fricas (because I like calculators) (add-to-list 'load-path "/usr/lib/fricas/emacs/") (use-package fricas :ensure nil @@ -1762,10 +1783,14 @@ If no name is given, list all bookmarks instead." (setq-local evil-lookup-func #'helpful-at-point)) (defun my/-setup-helpful-mode () (setq-local evil-lookup-func #'helpful-at-point)) - (defvar my/helpful-symbol-history-size 20 + (defvar my/helpful-symbol-history-size 50 "Max size of `my/helpful-symbol-history'.") (defvar my/helpful-symbol-history '() "History of helpful symbols.") + (defvar my/-helpful-inhibit-history nil + "If non-nil, don't add symbols to `my/helpful-symbol-history'.") + (defvar my/-helpful-last-entry nil + "Last entry looked up with helpful.") (defun my/helpful-history-back (count) "Go back COUNT symbols in `my/helpful-symbol-history'. If called interactively, COUNT defaults to 1." @@ -1791,7 +1816,8 @@ the oldest entry if -COUNT is larger than the history." ((and (< count 0) (= (1+ current-pos) hist-len)) (message "%s" "No older symbol!")) (t - (let ((entry (cond + (let ((my/-helpful-inhibit-history t) + (entry (cond ((<= new-pos 0) (seq-first my/helpful-symbol-history)) ((>= new-pos hist-len) @@ -1804,29 +1830,29 @@ the oldest entry if -COUNT is larger than the history." (defun my/-helpful-switch-buffer-function (helpful-buf) "Like `pop-to-buffer', but kill previous helpful buffers and save the new buffers `helpful--sym' to `my/helpful-symbol-history'." - (cl-loop for buf in (buffer-list) - with window = nil - with last-index = nil + (cl-loop with window = nil + for buf in (buffer-list) when (and (not (eq buf helpful-buf)) (eq (buffer-local-value 'major-mode buf) 'helpful-mode)) do (when-let (cur-window (get-buffer-window buf nil)) (setq window cur-window)) - (when-let (entry (buffer-local-value 'helpful--sym buf)) - (setq last-index (seq-position my/helpful-symbol-history entry 'equal))) (kill-buffer buf) finally - (when-let ((entry (cons (buffer-local-value 'helpful--sym helpful-buf) - (buffer-local-value 'helpful--callable-p - helpful-buf))) - ((not (seq-contains-p my/helpful-symbol-history entry - 'equal)))) - (when last-index + (let ((entry (cons (buffer-local-value 'helpful--sym helpful-buf) + (buffer-local-value 'helpful--callable-p + helpful-buf)))) + (unless my/-helpful-inhibit-history + (when-let (from-current-hist + (member my/-helpful-last-entry + my/helpful-symbol-history)) + (setq my/helpful-symbol-history from-current-hist)) + (cl-pushnew entry my/helpful-symbol-history :test 'equal) (setq my/helpful-symbol-history - (seq-drop my/helpful-symbol-history last-index))) - (push entry my/helpful-symbol-history) - (seq-take my/helpful-symbol-history my/helpful-symbol-history-size)) + (seq-take my/helpful-symbol-history + my/helpful-symbol-history-size))) + (setq my/-helpful-last-entry entry)) (if window (window--display-buffer helpful-buf window 'reuse) (pop-to-buffer helpful-buf)))) @@ -1957,7 +1983,6 @@ one of the normal rainbow-delimiters-depth-N-face faces." ;; page break lines (use-package page-break-lines - :hook (helpful-mode . page-break-lines-mode) :config (global-page-break-lines-mode 1) (add-to-list 'page-break-lines-modes 'prog-mode)