λ +ide -

Author: lispcat 187922791+lispcat@users.noreply.github.com

Configure Emacs for IDE functionality.

% templates for new files

(auto-insert-mode)  ;;; Adds hook to find-files-hook

% sane indentation defaults

(setq-default indent-tabs-mode nil)
(setq tab-always-indent t)

% tweak compilation buffer

(leaf compile :elpaca nil
  :config
  (setq compilation-scroll-output t))

% enable syntax checker

(leaf flycheck
  :hook prog-mode-hook)

% buttonize URLs

(leaf emacs :elpaca nil
  :hook goto-address-mode)

% project.el

(leaf project :elpaca nil
  :bind-keymap ("C-c P" . project-prefix-map)
  :init
  (defun project-compile-interactive ()
    (declare (interactive-only compile))
    (interactive)
    (let ((current-prefix-arg '(4)))
      (call-interactively #'project-compile)))
  :bind
  (project-prefix-map
   ("C" . project-compile-interactive)))

(leaf projectile
  :init
  (projectile-mode 1)
  :bind-keymap
  ("C-c p" . projectile-command-map)
  :config
  (setq projectile-compile-use-comint-mode t))

% lsp-mode

(leaf lsp-mode
  :commands (lsp lsp-deferred)

  :hook (lsp-mode-hook . lsp-enable-which-key-integration)

  :bind-keymap ("C-c l" . lsp-command-map)

  :config
  (setq lsp-inlay-hint-enable t
        ;; freq of refreshing highlights, lenses, links, etc
        lsp-idle-delay 0.5
        ;; bind "C-c l" to lsp-command-map
        lsp-keymap-prefix "C-c l"
        ;; problematic: https://github.com/emacs-lsp/lsp-mode/issues/4113
        lsp-update-inlay-hints-on-scroll nil))

> lsp-ui

(leaf lsp-ui
  :bind
  (lsp-ui-mode-map
   ([remap xref-find-definitions] . lsp-ui-peek-find-definitions)
   ([remap xref-find-references]  . lsp-ui-peek-find-references))
  (lsp-ui-doc-frame-mode-map
   ("q" . lsp-ui-doc-hide)
   ("u" . lsp-ui-doc-unfocus-frame))
  :config
  (setq lsp-ui-doc-delay 0.5
        lsp-ui-doc-position 'top
        ;; lsp-ui-doc-alignment 'window
        lsp-ui-doc-alignment 'frame
        ;; lsp-ui-doc-show-with-mouse nil
        lsp-ui-doc-show-with-mouse t
        lsp-ui-doc-show-with-cursor t

        lsp-ui-sideline-delay 0.2

        lsp-ui-imenu-auto-refresh-delay 1.0)

  (with-eval-after-load 'lsp-mode
    (define-key lsp-command-map (kbd "v i") #'lsp-ui-imenu)))

> lsp-booster

;; use lsp-doctor for testing
;; Steps:
;; - install emacs-lsp-booster
;; - use plist for deserialization (FOLLOW GUIDE)
(leaf emacs :elpaca nil
  :config
  (setq read-process-output-max (* 1024 1024)) ;; 1mb
  (defun lsp-booster--advice-json-parse (old-fn &rest args)
    "Try to parse bytecode instead of json."
    (or
     (when (equal (following-char) ?#)
       (let ((bytecode (read (current-buffer))))
         (when (byte-code-function-p bytecode)
           (funcall bytecode))))
     (apply old-fn args)))
  (advice-add (if (progn (require 'json)
                         (fboundp 'json-parse-buffer))
                  'json-parse-buffer
                'json-read)
              :around
              #'lsp-booster--advice-json-parse)
  (defun lsp-booster--advice-final-command (old-fn cmd &optional test?)
    "Prepend emacs-lsp-booster command to lsp CMD."
    (let ((orig-result (funcall old-fn cmd test?)))
      (if (and (not test?) ;; for check lsp-server-present?
               (not (file-remote-p default-directory)) ;; see lsp-resolve-final-command, it would add extra shell wrapper
               lsp-use-plists
               (not (functionp 'json-rpc-connection)) ;; native json-rpc
               (executable-find "emacs-lsp-booster"))
          (progn
            (when-let ((command-from-exec-path (executable-find (car orig-result)))) ;; resolve command from exec-path (in case not found in $PATH)
              (setcar orig-result command-from-exec-path))
            (message "Using emacs-lsp-booster for %s!" orig-result)
            (cons "emacs-lsp-booster" orig-result))
        orig-result)))
  (advice-add 'lsp-resolve-final-command :around
              #'lsp-booster--advice-final-command))

> Automatic parenthesis pair matching

;; for non-programming too
(leaf elec-pair :elpaca nil
  :require t
  :config
  ;; disable "<" pair expansion
  (defun +disable-<-pair-expansion ()
    (setq-local electric-pair-inhibit-predicate
                `(lambda (c)
                   (if (char-equal c ?<)
                       t
                     (,electric-pair-inhibit-predicate c)))))
  (add-hook 'org-mode-hook #'+disable-<-pair-expansion)
  ;; global
  (electric-pair-mode 1))

% langs

% Langs

> Lisp

(setq +lisp-mode-hooks
      '(emacs-lisp-mode-hook
        lisp-data-mode-hook
        scheme-mode-hook))
‣ rainbow parens
;; Highlight nested parens according to their depth.
;; ---

(leaf rainbow-delimiters
  :hook `,@+lisp-mode-hooks)
‣ paredit
(leaf paredit
  :hook `,@+lisp-mode-hooks)
‣ emacs-lisp
(leaf emacs :elpaca nil
  :hook ((emacs-lisp-mode-hook . (lambda ()
                                   (auto-fill-mode)
                                   (setq-local fill-column 80)))))
(leaf orglink
  :hook emacs-lisp-mode-hook)
‣ elisp misc
(defun create-banner-comment (text &optional width)
  "Create a banner comment with TEXT centered between semicolons.
Optional WIDTH parameter determines total width (defaults to 70)."
  (interactive "sText: ")
  (let* ((width (or width 70))
         (text-len (length text))
         (semi-len (/ (- width text-len 2) 2)) ; -2 for spaces
         (left-semis (make-string semi-len ?\;))
         (right-semis (make-string
                       (if (cl-oddp (- width text-len))
                           (1+ semi-len)
                         semi-len)
                       ?\;)))
    (insert (format "%s %s %s\n"
                    left-semis
                    text
                    right-semis))))
‣ tweak flycheck for elisp
(with-eval-after-load 'flycheck
  (setq-default flycheck-disabled-checkers '(emacs-lisp-checkdoc)))
‣ scheme
(leaf scheme-mode :elpaca nil
  :disabled t
  :mode "\\.sld\\'" "\\.scm\\'")

(leaf geiser
  :disabled t
  :mode "\\.scm\\'"
  :setq
  (geiser-default-implementation . 'guile)
  (geiser-active-implementations . '(guile))
  (geiser-implementations-alist  . '(((regexp "\\.scm$") guile))))

(leaf geiser-guile
  :disabled t
  :after geiser)
‣ Clojure
(leaf clojure-mode
  :disabled t) ;;;; Rust

(leaf rust-mode
  :require t
  :init
  (setq rust-mode-treesitter-derive t)
  (setq rust-rustfmt-switches '("--edition" "2021")))

(leaf rustic
  :require t
  :after rust-mode
  :config
  (setq rustic-cargo-use-last-stored-arguments t)
  (setq rustic-format-on-save t)
  (setq rustic-rustfmt-args "--edition 2021")

  ;; lsp-mode settings
  (with-eval-after-load 'lsp-mode
    (setq lsp-rust-analyzer-cargo-watch-command "clippy"
          lsp-rust-analyzer-display-closure-return-type-hints t ; def: nil
          lsp-rust-analyzer-display-lifetime-elision-hints-enable "skip_trivial"
          lsp-rust-analyzer-display-parameter-hints t ; def: nil (input param name)

          ;; maybe
          ;; lsp-rust-analyzer-display-reborrow-hints "mutable" ; def: never (&*(&*jargon))
          lsp-rust-analyzer-display-lifetime-elision-hints-use-parameter-names t ; def: nil (?)

          ;; experimenting
          lsp-signature-auto-activate t ; def: '(:on-trigger-char :on-server-request)
          ))

  ;; use tree-sitter for rustic-mode
  ;; (define-derived-mode rustic-mode rust-ts-mode "Rustic"
  ;;     "Major mode for Rust code.

  ;; \\{rustic-mode-map}"
  ;;     :group 'rustic
  ;;     (when (bound-and-true-p rustic-cargo-auto-add-missing-dependencies)
  ;;       (add-hook 'lsp-after-diagnostics-hook 'rustic-cargo-add-missing-dependencies-hook nil t)))

  :bind
  (rustic-mode-map
   ("C-c C-c M-r" . rustic-cargo-comint-run)
   ("C-c C-c l" . flycheck-list-errors)
   ("C-c C-c A" . rustic-cargo-add)
   ("C-c C-c R" . rustic-cargo-rm)
   ("C-c C-c a" . lsp-execute-code-action)
   ("C-c C-c r" . lsp-rename)
   ("C-c C-c q" . lsp-workspace-restart)
   ("C-c C-c Q" . lsp-workspace-shutdown)
   ("C-c C-c s" . lsp-rust-analyzer-status)
   ("C-c C-c h" . lsp-describe-thing-at-point))

  :hook
  (rust-ts-mode-hook . (lambda ()
                         ;; company settings
                         (with-eval-after-load 'company
                           (setq-local company-idle-delay 0.3
                                       company-minimum-prefix-length 2))
                         ;; lsp settings
                         (with-eval-after-load 'lsp-mode
                           (setq-local lsp-idle-delay 0.5
                                       lsp-ui-sideline-delay 0.3
                                       lsp-eldoc-render-all nil ; def: nil (minibuffer doc popup)
                                       lsp-ui-doc-enable t ; def: t (ui-popup docs)
                                       lsp-ui-doc-max-height 14 ; def: 13
                                       )))))


;; (leaf rustic :elpaca nil
;;   ;; :disabled t
;;   :if use-eglot?
;;   :init
;;   (setq rustic-lsp-client 'eglot)
;;   (with-eval-after-load 'eglot
;;     (let ((rust-init-options
;;            `(
;;              :cargo       ( :buildScripts (:enable t) :features "all" )
;;              :procMacro   ( :enable t )
;;              :checkOnSave ( :command "clippy" )
;;              :inlayHints  ( :typeHints t
;;                             :parameterHints t
;;                             :closureReturnTypeHints t
;;                             :lifetimeElisionHints (:enable "skip_trivial" :useParameterNames t)
;;                             :reborrowHints "mutable"
;;                             ;; :chainingHints t
;;                             )
;;              )))
;;       (add-to-list 'eglot-server-programs
;;                    `(rustic-mode . ("rust-analyzer"
;;                                     :initializationOptions ,rust-init-options)))))
;;   ;; :config

;;   )


;; rustowl
;; (straight-use-package
;;  `(rustowlsp
;;    :host github
;;    :repo "cordx56/rustowl"
;;    :files (:defaults "emacs/*")))

> C

(leaf cc-mode :elpaca nil
  :hook ((c-mode-hook . lsp)
         (c-mode-hook . (lambda ()
                          (setq-local lsp-idle-delay 0.1
                                      lsp-enable-indentation nil
                                      lsp-enable-on-type-formatting nil)
                          (c-set-offset 'case-label '+))))
  :config
  (add-to-list 'c-default-style '(c-mode . "cc-mode"))
  (define-key c-mode-map (kbd "<f8>") #'project-compile-interactive))

;; (leaf cc-mode :elpaca nil
;;   :if use-eglot?
;;   :hook ((c-mode-hook . eglot-ensure)
;;          (c-mode-hook . (lambda ()
;;                           ;; (setq-local lsp-idle-delay 0.1
;;                           ;;             lsp-enable-indentation nil
;;                           ;;             lsp-enable-on-type-formatting nil)
;;                           (c-set-offset 'case-label '+))))
;;   :config
;;   (add-to-list 'c-default-style '(c-mode . "cc-mode"))
;;   (define-key c-mode-map (kbd "<f8>") #'project-compile-interactive))

> Java

(leaf lsp-java
  :mode "\\.java\\'"
  :config
  (add-hook 'java-mode-hook #'lsp))

;; (leaf eglot-java
;;   :hook java-mode-hook
;;   :bind
;;   (eglot-java-mode-map
;;    ("C-c l n" . eglot-java-file-new)
;;    ("C-c l x" . eglot-java-run-main)
;;    ("C-c l t" . eglot-java-run-test)
;;    ("C-c l N" . eglot-java-project-new)
;;    ("C-c l T" . eglot-java-project-build-task)
;;    ("C-c l R" . eglot-java-project-build-refresh)))

> Markdown

(leaf markdown-mode
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode))
  :setq
  (markdown-fontify-code-blocks-natively . t)
  :config
  (defun +setup-markdown-mode ()
    ;; (visual-fill-column-mode 1)
    (display-line-numbers-mode 0))

  ;; (setq markdown-command "marked")
  (add-hook 'markdown-mode-hook #'+setup-markdown-mode))

> Scala

(leaf scala-mode
  :disabled t
  :interpreter "scala"
  :hook
  (lambda () (setq prettify-symbols-alist
              scala-prettify-symbols-alist)))

> Zig

(leaf zig-mode
  :disabled t
  ;; :config
  ;; (zig-format-on-save-mode 0)
  )

> Haskell

(leaf haskell-mode
  :mode "\\.hs\\'")

> Nix

(leaf nix-mode
  :mode "\\.nix\\'"
  :hook ((nix-mode-hook . lsp)))

> Yaml

(leaf yaml-mode
  :mode "\\.yml\\'")

> Ron

(leaf ron-mode
  :require t)

> Kerolox

(leaf emacs :elpaca nil ;; kerolox!

  ;; Major-mode for .rp1 files
  (define-derived-mode kerolox-mode prog-mode "kerolox"
    "Major mode for editing kerolox (.rp1) files."
    :group 'kerolox)

  (with-eval-after-load 'lsp-mode
    ;; Register LSP server and setup LSP server
    (add-to-list 'lsp-language-id-configuration '(kerolox-mode . "kerolox"))
    (lsp-register-client
     (make-lsp-client
      :new-connection (lsp-stdio-connection '("/home/sui/Code/cloned/saturn-v/target/release/saturn-v" "lsp"))
      :major-modes '(kerolox-mode)
      :server-id 'saturn-v-lsp)))
‣ Kerolox treesit mode and LSP
  (define-derived-mode kerolox-ts-mode kerolox-mode "kerolox[ts]"
    "Tree-sitter based major mode for editing kerolox (.rp1) files."
    :group 'kerolox

    (when (and (fboundp 'treesit-available-p)
               (treesit-available-p))

      ;; create parser for this buffer
      (treesit-parser-create 'kerolox)

      (setq-local treesit-font-lock-feature-list
                  '((comment)
                    (keyword string)
                    (constant type)
                    (function variable module constructor)
                    (operator punctuation)))

      (setq-local font-lock-defaults nil)

      ;; Set up face mapping for tree-sitter query capture names to Emacs faces
      (defvar kerolox-ts-font-lock-settings
        (treesit-font-lock-rules
         :language 'kerolox
         :feature 'comment
         '((comment) @font-lock-comment-face)

         :language 'kerolox
         :feature 'constant
         '((integer) @font-lock-constant-face
           (value (symbol)) @font-lock-constant-face)

         :language 'kerolox
         :feature 'variable
         '((variable) @font-lock-variable-name-face)

         :language 'kerolox
         :feature 'module
         '((import (symbol)) @font-lock-preprocessor-face)

         :language 'kerolox
         :feature 'type
         '((type (symbol)) @font-lock-type-face)

         :language 'kerolox
         :feature 'function
         '((definition relation: (symbol)) @font-lock-function-name-face
           (atom head: (symbol)) @font-lock-function-name-face)

         :language 'kerolox
         :feature 'constructor
         '((rule relation: (symbol)) @font-lock-function-name-face)

         :language 'kerolox
         :feature 'punctuation
         '(([":-" "," "."]) @font-lock-delimiter-face
           (["(" ")"]) @font-lock-bracket-face)

         :language 'kerolox
         :feature 'operator
         '((binary_expr op: (_)) @font-lock-builtin-face
           (unary_expr op: (_)) @font-lock-builtin-face
           (cardinality kind: (_)) @font-lock-builtin-face)

         :language 'kerolox
         :feature 'keyword
         '((["constrain" "decision" "define" "import" "output" "soft"]) @font-lock-keyword-face
           (constraint_kind) @font-lock-keyword-face))
        "Font-lock settings for Kerolox.")

      ;; Set font-lock settings from the defined rules
      (setq-local treesit-font-lock-settings kerolox-ts-font-lock-settings)

      (treesit-major-mode-setup)))

  ;; Register LSP server and setup LSP server
  (with-eval-after-load 'lsp-mode
    (add-to-list 'lsp-language-id-configuration '(kerolox-ts-mode . "kerolox"))
    (lsp-register-client
     (make-lsp-client
      :new-connection (lsp-stdio-connection '("/home/sui/Code/cloned/saturn-v/target/release/saturn-v" "lsp"))
      :major-modes '(kerolox-ts-mode)
      :server-id 'saturn-v-ts-lsp)))
‣ kerolox - tree-sitter generic
  (with-eval-after-load 'treesit
    ;; Configure the language grammar source and mapping
    (when (and (fboundp 'treesit-available-p)
               (treesit-available-p))
      ;; Define grammar source
      (add-to-list 'treesit-language-source-alist
                   '(kerolox . ("https://github.com/marceline-cramer/saturn-v" nil "tree-sitter-kerolox/src")))

      ;; ;; Set up language mapping
      ;; (add-to-list 'treesit-language-remap-alist '(kerolox-ts-mode . kerolox))

      ;; Only install if not already installed
      ;; (unless (treesit-language-available-p 'kerolox)
      ;;   (treesit-install-language-grammar 'kerolox))
      (treesit-install-language-grammar 'kerolox)
      ))

  ;; Auto-start LSP when opening .rp1 files with tree-sitter mode
  (add-hook 'kerolox-ts-mode-hook #'lsp-deferred)
‣ Kerolox misc
  ;; Remap regular mode to tree-sitter mode
  (setq major-mode-remap-alist
        '((kerolox-mode . kerolox-ts-mode)))
‣ Kerolox - Auto-mode-alist
  ;; Associate file name pattern with major-mode
  (add-to-list 'auto-mode-alist '("\\.rp1\\'" . kerolox-ts-mode)))

> Lua-mode

(leaf lua-mode
  :config
  (with-eval-after-load 'lsp-lua
    ;; fix issue with externally installed server
    (setq lsp-clients-lua-language-server-command
          "lua-language-server")
    ;; renoise lua api definitions
    ;; (setq lsp-lua-workspace-library "'Lua.workspace.library': {'/home/sui/Music/prod/scripts/renoise-lua/definitions': true}")
    (setq lsp-lua-workspace-library (ht ("/home/sui/Music/prod/scripts/renoise-lua/definitions" t)))
    (setq lsp-lua-runtime-plugin "/home/sui/Music/prod/scripts/renoise-lua/definitions/plugin.lua")
    )

  ;; fix pt.2
  (defun +lsp-clients-lua-language-server-test ()
    "(Improved) Test Lua language server binaries and files."
    (or (and (f-exists? lsp-clients-lua-language-server-main-location)
             (f-exists? lsp-clients-lua-language-server-bin))
        (f-exists? (car (split-string lsp-clients-lua-language-server-command)))))

  (advice-add #'lsp-clients-lua-language-server-test
              :override
              #'+lsp-clients-lua-language-server-test))

> Typst

(use-package typst-ts-mode
  :ensure (:type git :host codeberg :repo "meow_king/typst-ts-mode")
  :demand t
  :custom
  (typst-ts-mode-grammar-location (expand-file-name
                                   "tree-sitter/libtree-sitter-typst.so"
                                   user-emacs-directory)))

% Tooling

> direnv

(leaf direnv
  :init
  (direnv-mode 1))

> Rainbow mode

Add color to hex codes in buffer.

  :hook prog-mode-hook)

> Ansi-color... not sure what this is for

(with-eval-after-load 'ansi-color
  (add-hook 'compilation-filter-hook 'ansi-color-compilation-filter))

> Auto-install treesitter backends

(leaf treesit-auto
  :require t
  :config
  (setq treesit-auto-install 'prompt)
  (global-treesit-auto-mode))

% Code formatting

% Code folding

;; TODO: look into: https://github.com/tarsius/outline-minor-faces
;; is this worth it? alternative of

> Outline

(leaf outline-indent
  :doc "Optimal folding: https://github.com/jamescherti/outline-indent.el"
  :commands outline-indent-minor-mode
  :after org
  :custom
  (outline-indent-ellipsis . " ▼")
  ;; (outline-blank-line . t)

  :init

  ;; outline-cycle
  (defun +outline-toggle (&optional meta)
    (interactive)
    ;; go to prev heading
    (outline-back-to-heading)
    ;; toggle
    (if (not (outline-invisible-p (pos-eol)))
        ;; hide
        (progn
          ;; hide below, only headings
          (outline-hide-subtree)
          (outline-show-branches))
      ;; show
      (if meta
          ;; show subtree
          (outline-show-subtree)
        ;; show current
        (outline-show-entry))))

  (defun +outline-toggle-meta ()
    (interactive)
    (+outline-toggle 't))

  ;; (defhydra +outline-cycle-hydra ()
  ;;   (";" +outline-cycle)
  ;;   ("<backtab>" +outline-cycle))

  ;; outline-cycle buffer
  (defun +outline-cycle-buffer (&optional level)
    (interactive (list (when current-prefix-arg
                         (prefix-numeric-value current-prefix-arg))))
    (let (top-level)
      (save-excursion
        (goto-char (point-min))
        (while (not (or (eq top-level 1) (eobp)))
          (when-let ((level (and (outline-on-heading-p t)
                                 (funcall outline-level))))
            (when (< level (or top-level most-positive-fixnum))
              (setq top-level (max level 1))))
          (outline-next-heading)))
      (cond
       (level
        (outline-hide-sublevels level)
        (setq outline--cycle-buffer-state 'all-heading)
        (message "All headings up to level %s" level))
       ((or (eq outline--cycle-buffer-state 'show-all)
            (eq outline--cycle-buffer-state 'top-level))
        (outline-show-all)
        (outline-hide-region-body (point-min) (point-max))
        (setq outline--cycle-buffer-state 'all-heading)
        (message "All headings"))
       (t
        (outline-show-all)
        (setq outline--cycle-buffer-state 'show-all)
        (message "Show all")))))

  (defhydra +outline-cycle-buffer-hydra ()
    (";" +outline-cycle-buffer)
    ("<backtab>" +outline-cycle-buffer))

  ;; outline-cycle hydra
  ;; (defun +outline-cycle-at-root (arg)
  ;;   (interactive "P")
  ;;   (let ((prev-loc (point-marker)))
  ;;     (if arg
  ;;         (progn
  ;;           (end-of-line) (outline-previous-heading)
  ;;           (+outline-cycle-buffer)
  ;;           (+outline-cycle-buffer-hydra/body))
  ;;       (end-of-line) (outline-previous-heading)
  ;;       (+outline-cycle)
  ;;       (+outline-cycle-hydra/body))
  ;;     (goto-char prev-loc)))

  ;; run outline-hide-body only after first focus (add to .dir-locals.el)
  ;; (defun +hide-outline-on-open (func &rest args)
  ;;   "Hide outlines when opening files via dired or projectile."
  ;;   (let ((result (apply func args)))
  ;;     ;; After the file is opened, hide outlines if conditions are met
  ;;     (when (and (buffer-file-name)
  ;;                outline-indent-minor-mode)
  ;;       (outline-hide-body))
  ;;     result))

  ;; (advice-add 'find-file :around #'+hide-outline-on-open)
  ;; (advice-add 'dired-find-file :around #'+hide-outline-on-open)
  ;; (advice-add 'projectile-find-file :around #'+hide-outline-on-open)
  ;; (advice-add 'projectile-find-file-dwim :around #'+hide-outline-on-open)

  ;; special TAB, cycle if on heading
  (defun +indent-for-tab-command--outline-advice (orig-fn &rest args)
    "Advice for alternative TAB behavior if over outline heading."
    (if (and (eq major-mode 'emacs-lisp-mode)
             (save-excursion
               (beginning-of-line)
               (looking-at "^;;;+ .*$")))
        (+outline-toggle)
      (apply orig-fn args)))

  (advice-add 'indent-for-tab-command :around
              #'+indent-for-tab-command--outline-advice)

  :bind

  (outline-minor-mode-map
   ("<backtab>" . +outline-toggle-meta))

  (emacs-lisp-mode-map
   ("C-c C-n" . outline-next-visible-heading)
   ("C-c C-p" . outline-previous-visible-heading))

  ("C-c ; ;" . +outline-cycle-buffer)
  ("C-c ; r" . +outline-cycle-at-root)

  ;; buffer
  ("C-c ; s" . outline-show-all)
  ("C-c ; h" . outline-hide-body)
  ("C-c ; b" . outline-show-all)
  ("C-c ; B" . outline-hide-body)

  ;; point
  ("C-c ; p" . outline-show-entry)
  ("C-c ; P" . outline-hide-entry)

  ;; subtree
  ("C-c ; t" . outline-show-subtree)
  ("C-c ; T" . outline-hide-subtree)

  ;; navigation/content/structure
  ("C-c ; n" . outline-show-branches)
  ("C-c ; N" . outline-hide-leaves)

  ;; other/current
  ("C-c ; O" . outline-hide-other)

  ;; children
  ("C-c ; c" . outline-show-children)
  ("C-c ; C" . outline-hide-children)

  :hook
  ((emacs-lisp-mode-hook
    . (lambda ()
        (outline-indent-minor-mode)
        (setq-local make-window-start-visible t)
        (let ((header-comment-p "^\\(;;;+\\) .*")
              (cob-p (string-join
                      '("^;;;;+$"
                        "^;;+ +\\(.*\\) +;$")
                      "\\|"))
              (coh-p (string-join
                      '("^;;; -- \\(.*\\) -+$")))
              (def-p (string-join
                      '("^("))))
          (setq-local outline-regexp
                      (string-join
                       (list
                        ;; cob-p
                        ;; coh-p
                        ;; def-p
                        header-comment-p)
                       ;; '(
                       ;;  ;; "^;;;+ .*"    ; ;;;+ space rest       (regular)
                       ;;  "^;;+ .*"     ; ;;+ space rest (optimal?)
                       ;;  "^;;$"        ; ^;;$ (alt)
                       ;;  "^(...."      ; top-level parens
                       ;;  ;; "^;;+ .* ;$"  ; ;;+ space rest ;      (cob)
                       ;;  "^;;;;+$"     ; ;;;;+ (only, all the way) (cob)
                       ;;  )
                       "\\|"))
          (setq-local outline-level
                      (lambda ()
                        (cond
                         ;; ((looking-at cob-p)
                         ;;  1)
                         ;; ((looking-at coh-p)
                         ;;  2)
                         ((looking-at "^\\(;;;+\\) .*")
                          (- (match-end 1) (match-beginning 1) 2))
                         ((looking-at def-p)
                          1000)
                         (t 0))))

          ;; (setq-local outline-level
          ;;             (lambda ()
          ;;               (cond
          ;;                ;; ((looking-at cob-p) 1)
          ;;                ;; ((looking-at coh-p) 2)
          ;;                ;; ((looking-at cow-p) 3)
          ;;                ((looking-at def-p)
          ;;                 (- (match-end 0) (match-beginning 0))))))
          ;; (setq-local outline-level
          ;;             (lambda ()
          ;;               (cond
          ;;                ((looking-at "^;;;+")
          ;;                 (- (match-end 0) (match-beginning 0)))
          ;;                ((looking-at "^(")
          ;;                 1000)
          ;;                nil)))
          )))))

> Outline faces

(leaf outline-minor-faces
  :after outline outline-indent
  :hook (outline-minor-mode-hook . outline-minor-faces-mode)
  :config
  ;; exclude custom fontlocking for
  (defun +outline-minor-faces--exclude-defuns (orig-fn arg)
    "Remove ^( patterns from the regex argument."
    (let ((filtered-regex
           (or (let ((regex "\\|^("))   ; Fixed: escaped the backslash properly
                 (and (string-search regex arg)
                      (string-replace regex "" arg))) ; Fixed: "" instead of nil
               (let ((regex "^(\\|"))                 ; Fixed: escaped properly
                 (and (string-search regex arg)
                      (replace-regexp-in-string regex "" arg))) ; Fixed: "" instead of nil
               (let ((regex "^("))
                 (and (string-search regex arg)
                      (replace-regexp-in-string regex "" arg)))))) ; Fixed: "" instead of nil
      (if filtered-regex
          (funcall orig-fn filtered-regex)
        (funcall orig-fn arg))))
  (advice-add 'outline-minor-faces--syntactic-matcher :around
              #'+outline-minor-faces--exclude-defuns))

(leaf backline
  :after outline outline-indent
  :config (advice-add 'outline-flag-region :after 'backline-update))

% end

(provide '+ide)

% +ide.el ends here


Last updated: August 22, 2025