From cffe715e3017b8ab9a956b1dab967f83d72a3b49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Madeleine=20Sydney=20=C5=9Alaga?= Date: Sat, 29 Aug 2026 03:03:18 -0600 Subject: [PATCH] feat(emacs): haskell-ts-mode --- modules/home/haskell.nix | 16 ++++- .../home/users/msyds/emacs/lisp/syd/comint.el | 3 +- .../users/msyds/emacs/lisp/syd/haskell.el | 61 ++++++++++++++++++- .../home/users/msyds/emacs/lisp/syd/tabs.el | 6 +- .../home/users/msyds/emacs/nix/treesit.nix | 35 +++++++++-- 5 files changed, 110 insertions(+), 11 deletions(-) diff --git a/modules/home/haskell.nix b/modules/home/haskell.nix index dda3918..460ce8b 100644 --- a/modules/home/haskell.nix +++ b/modules/home/haskell.nix @@ -12,8 +12,20 @@ in { builtins.readFile ./haskell/ghc-with-packages.sh; home.file.".ghc/ghci.conf".text = '' - :set prompt "\x1b[38;5;5mλ>\x1b[0m " - :set prompt-cont "\x1b[38;5;5m|>\x1b[0m " + :{ + ghci_set_prompt :: IO String + ghci_set_prompt = do + term <- System.Environment.Blank.getEnvDefault "TERM" "" + let (prompt,promptCont) = + if Data.List.isPrefixOf "xterm-" term + then ("\x1b[38;5;5mλ>\x1b[0m ", "\x1b[38;5;5m|>\x1b[0m ") + else ("λ> ", "|> ") + pure . unlines $ + [ ":set prompt " ++ show prompt + , ":set prompt-cont " ++ show promptCont + ] + :} + :cmd ghci_set_prompt :set -interactive-print=Text.Pretty.Simple.pPrint :m + Text.Show.Functions :unset +r diff --git a/modules/home/users/msyds/emacs/lisp/syd/comint.el b/modules/home/users/msyds/emacs/lisp/syd/comint.el index 01cc347..40e475b 100755 --- a/modules/home/users/msyds/emacs/lisp/syd/comint.el +++ b/modules/home/users/msyds/emacs/lisp/syd/comint.el @@ -18,7 +18,8 @@ :states '(normal insert) "C-k" #'comint-previous-input "C-j" #'comint-next-input - "C-s" #'consult-history) + "C-s" #'consult-history + "" #'comint-send-input) (general-def :keymaps 'comint-mode-map :states 'insert diff --git a/modules/home/users/msyds/emacs/lisp/syd/haskell.el b/modules/home/users/msyds/emacs/lisp/syd/haskell.el index b27fbf5..a732174 100644 --- a/modules/home/users/msyds/emacs/lisp/syd/haskell.el +++ b/modules/home/users/msyds/emacs/lisp/syd/haskell.el @@ -9,6 +9,18 @@ (require 'inf-haskell) (run-haskell)) +(defun syd-haskell-ts-open-repl () + "Open a Haskell REPL." + (interactive) + (haskell-ts-run) + (pop-to-buffer haskell-ts-ghci-buffer-name)) + +(defun syd-haskell-ts-eval-region (beg end) + (haskell-ts--send-region beg end)) + +(defun syd-haskell-ts-load-buffer () + (haskell-ts-load-file)) + (defun syd-haskell-evil-open-above () "Opens a line above the current, following Haskell-mode's indentation" (interactive) @@ -38,7 +50,9 @@ :custom (; Show errors in REPL, not popup buffers. (haskell-interactive-popup-errors nil) (haskell-process-suggest-remove-import-line t) - (haskell-process-auto-import-loaded-modules t)) + (haskell-process-auto-import-loaded-modules t) + (haskell-ts-inferior-history-file + (file-name-concat syd-data-dir "haskell-ts-inferior-history"))) :general (:keymaps 'haskell-mode-map :states '(normal visual motion emacs insert) @@ -62,13 +76,56 @@ (add-to-list 'haskell-font-lock-quasi-quote-modes '("wats" . wat-mode)) (add-to-list 'haskell-font-lock-quasi-quote-modes '("expr" . wat-mode))) +(use-package consult-hoogle + :config + (syd-push shackle-rules + `("*hoogle-search*" + :select t :size 0.5 :popup t :align bottom))) + +(use-package haskell-ts-mode + :straight (:type git + :host github + :repo "dschrempf/haskell-ts-mode") + :custom ((haskell-ts-font-lock-level 4) + (haskell-ts-ghci "ghci") + (haskell-ts-inferior-prompt-regexp + (rx bol (or "λ> " "|> ")))) + :general + (:keymaps 'haskell-ts-mode-map + :states '(normal motion) + :major-modes t + :prefix syd-localleader-key + :non-normal-prefix syd-alt-localleader-key + "h s" #'syd-haskell-hoogle-start-server + "h q" #'haskell-hoogle-kill-server + "c" #'haskell-cabal-visit-file) + :config + (add-to-list 'completion-ignored-extensions ".hi") + (syd-add-hook 'haskell-ts-mode-hook + (defun syd-haskell-configure-indent-h () + (setq-local tab-width 2) + ;; (setq-local indent-line-function #'tab-to-tab-stop) + (indent-tabs-mode -1)) + (defun syd-haskell-configure-evil-h () + (setq-local evil-symbol-word-search nil))) + (syd-handle '(haskell-ts-mode) + :repl #'syd-haskell-ts-open-repl + :eval-region #'syd-haskell-ts-eval-region + :load-buffer #'syd-haskell-ts-load-buffer) + (syd-defadvice syd-haskell-hide-repl-h (old &rest rs) + :around #'haskell-ts-show-repl + (save-window-excursion + (apply old rs)))) + (use-package lsp-haskell :defer t :custom ((lsp-haskell-server-path "haskell-language-server")) :init (add-hook 'haskell-mode-hook #'lsp-deferred 'append) (add-hook 'haskell-literate-mode-hook #'lsp-deferred 'append) - (syd-add-hook '(haskell-mode-hook haskell-literate-mode-hook) + (add-hook 'haskell-ts-mode-hook #'lsp-deferred 'append) + (syd-add-hook '(haskell-ts-mode-hook + haskell-mode-hook haskell-literate-mode-hook) (defun syd-haskell-configure-lsp-h () ;; it often highlights list literals and quasiquotes which ;; gets reaaaally annoying. diff --git a/modules/home/users/msyds/emacs/lisp/syd/tabs.el b/modules/home/users/msyds/emacs/lisp/syd/tabs.el index d5d7d31..a219bfb 100644 --- a/modules/home/users/msyds/emacs/lisp/syd/tabs.el +++ b/modules/home/users/msyds/emacs/lisp/syd/tabs.el @@ -1,7 +1,11 @@ ;;; -*- lexical-binding: t -*- (require 'syd/base) -;; Disable tabs by default. +;; Tabs, not spaces. (setq-default indent-tabs-mode nil) +(setq-default tab-width 2) + +(use-package kakapo-mode + :hook ((prog-mode . kakapo-mode))) (provide 'syd/tabs) diff --git a/modules/home/users/msyds/emacs/nix/treesit.nix b/modules/home/users/msyds/emacs/nix/treesit.nix index a0b4e8c..f1b4994 100644 --- a/modules/home/users/msyds/emacs/nix/treesit.nix +++ b/modules/home/users/msyds/emacs/nix/treesit.nix @@ -6,9 +6,34 @@ in { enable = lib.mkEnableOption "Treesitter grammars"; }; - config = lib.mkIf cfg.enable { - sydnix.emacs.emacsPackages = epkgs: [ - epkgs.treesit-grammars.with-all-grammars - ]; - }; + config = lib.mkIf cfg.enable + # https://github.com/dschrempf/haskell-ts-mode/blob/1c236f00dc214e546124a5dac90902ba62500c15/flake.nix#L40-L55 + (let + grammars = pkgs.tree-sitter-grammars.overrideScope (ts-final: ts-prev: { + tree-sitter-haskell = ts-prev.tree-sitter-haskell.overrideAttrs (prev: + let rev = "1ad6077a1fb776c255836e00aeb6da57ba564b6a"; + in { + version = "unstable-2026-07-07-${builtins.substring 0 7 rev}"; + src = pkgs.fetchFromGitHub { + owner = "dschrempf"; + repo = "tree-sitter-haskell"; + inherit rev; + hash = "sha256-kC6HwImFRx5HUY7NDLAA0I/DaSstQ94dWxA9+lMx2gI="; + }; + nativeBuildInputs = (prev.nativeBuildInputs or [ ]) ++ [ + pkgs.nodejs + pkgs.tree-sitter + ]; + preBuild = '' + tree-sitter generate + ''; + }); + }); + in { + sydnix.emacs.emacsPackages = epkgs: [ + (epkgs.treesit-grammars.with-grammars (_ps: + grammars.allGrammars + )) + ]; + }); }