feat(emacs): Haskell

This commit is contained in:
Madeleine Sydney
2025-03-02 20:50:53 -07:00
parent cf8b6e7ba1
commit 3dbaa864d4
10 changed files with 242 additions and 2 deletions

View File

@@ -51,11 +51,13 @@
#'cider-eval-region)) #'cider-eval-region))
:custom ((cider-show-error-buffer nil)) :custom ((cider-show-error-buffer nil))
:general :general
;; DEPRECATED: Remove once a `map!' equivalent is implemented.
(:keymaps 'cider-repl-mode-map (:keymaps 'cider-repl-mode-map
:states '(normal insert) :states '(normal insert)
"C-k" #'cider-repl-backward-input "C-k" #'cider-repl-backward-input
"C-j" #'cider-repl-forward-input "C-j" #'cider-repl-forward-input
"C-s" #'cider-repl-previous-matching-input) "C-s" #'cider-repl-previous-matching-input)
;; DEPRECATED: Remove once a `map!' equivalent is implemented.
(:keymaps 'clojure-mode-map (:keymaps 'clojure-mode-map
:states '(normal visual motion emacs insert) :states '(normal visual motion emacs insert)
:major-modes t :major-modes t

View File

@@ -0,0 +1,69 @@
;;; syd-lang-haskell.el -*- lexical-binding: t; -*-
(require 'syd-handle-repl)
(require 'syd-handle-lookup)
(defun syd-haskell-open-repl ()
"Open a Haskell REPL."
(interactive)
(require 'inf-haskell)
(run-haskell))
(defun syd-haskell-evil-open-above ()
"Opens a line above the current, following Haskell-mode's indentation"
(interactive)
(evil-beginning-of-line)
(haskell-indentation-newline-and-indent)
(evil-previous-line)
(haskell-indentation-indent-line)
(evil-append-line nil))
(defun syd-haskell-evil-open-below ()
"Opens a line below the current, following Haskell-mode's indentation"
(interactive)
(evil-append-line nil)
(haskell-indentation-newline-and-indent))
(use-package haskell-mode
:mode (("\\.l?hs'" . haskell-literate-mode)
("\\.hs'" . haskell-mode))
: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))
:general
;; DEPRECATED: Remove once a `map!' equivalent is implemented.
(:keymaps 'haskell-mode-map
:states '(normal visual motion emacs insert)
:major-modes t
:prefix syd-localleader-key
:non-normal-prefix syd-alt-localleader-key
"c" #'haskell-cabal-visit-file
"h s" #'haskell-hoogle-start-server
"h q" #'haskell-hoogle-kill-server)
(general-def :keymaps 'interactive-haskell-mode-map
:states '(normal insert)
"C-j" #'haskell-interactive-mode-history-next
"C-k" #'haskell-interactive-mode-history-previous)
:config
(set-repl-handler! '(haskell-mode haskell-cabal-mode literate-haskell-mode)
#'syd-haskell-open-repl
;; Haskell-mode provides IDE features by communicating with a persistent
;; REPL process à la Lisp.
:persist t)
(add-to-list 'completion-ignored-extensions ".hi")
;; Don't kill REPL popup on ESC/C-g
(set-popup-rule! "^\\*haskell\\*" :quit nil)
(syd-add-hook 'haskell-mode-local-vars-hook
;; Folding of Haskell sections.
#'haskell-collapse-mode
#'interactive-haskell-mode))
(use-package lsp-haskell
:defer t
:init
(add-hook 'haskell-mode-local-vars-hook #'lsp 'append)
(add-hook 'haskell-literate-mode-local-vars-hook #'lsp 'append))
(provide 'syd-lang-haskell)

View File

@@ -4,5 +4,6 @@
(require 'syd-lang-emacs-lisp) (require 'syd-lang-emacs-lisp)
(require 'syd-lang-clojure) (require 'syd-lang-clojure)
(require 'syd-lang-nix) (require 'syd-lang-nix)
(require 'syd-lang-haskell)
(provide 'syd-lang) (provide 'syd-lang)

View File

@@ -79,6 +79,9 @@
;; REVIEW: Is it safe to make this be async? We require that the command ;; REVIEW: Is it safe to make this be async? We require that the command
;; has finished before Git initialises. ;; has finished before Git initialises.
(skeletor-shell-command "nix run github:jlesquembre/clj-nix#deps-lock" (skeletor-shell-command "nix run github:jlesquembre/clj-nix#deps-lock"
dir)))) dir)))
(skeletor-define-template "haskell-flake"
:title "Haskell (Flake)"
:license-file-name "LICENSE"))
(provide 'syd-projects) (provide 'syd-projects)

View File

@@ -1,5 +1,17 @@
;;; syd-tooling.el -*- lexical-binding: t; -*- ;;; syd-tooling.el -*- lexical-binding: t; -*-
(defun syd-lsp-lookup-documentation ()
(interactive)
(when-let* ((buf (get-buffer "*lsp-help*")))
(kill-buffer buf))
(call-interactively #'lsp-describe-thing-at-point)
(let ((buf (get-buffer "*lsp-help*")))
(when (get-buffer-window-list buf)
;; Bury the buffer so the popup system has full control over how it's
;; selected.
(bury-buffer buf)
buf)))
(use-package lsp-mode (use-package lsp-mode
:init :init
;; We'll bind things ourselves. ;; We'll bind things ourselves.
@@ -25,7 +37,13 @@
(user-error (concat "Ignoring a call to `lsp-install-server'" (user-error (concat "Ignoring a call to `lsp-install-server'"
" — tell the caller to use Nix!"))) " — tell the caller to use Nix!")))
(set-popup-rule! (rx line-start "*lsp-" (or "help" "install")) (set-popup-rule! (rx line-start "*lsp-" (or "help" "install"))
:size 0.35 :quit t :select nil)) :size 13 :quit t :select nil)
;; DEPRECATED: Remove once syd-strategies is working.
(syd-add-hook 'lsp-mode
(defun syd-lsp-set-handlers-h ()
(setq-local syd-lookup-documentation-handlers
(list #'syd-lsp-lookup-documentation)))))
(use-package envrc (use-package envrc
;; REVIEW: Can we load this any later/better? ;; REVIEW: Can we load this any later/better?

View File

@@ -0,0 +1 @@
use flake

View File

@@ -0,0 +1,63 @@
cabal-version: 3.0
name: __PROJECT-NAME__
version: 0.1.0.0
synopsis: __DESCRIPTION__
description: __DESCRIPTION__
license: GPL-3.0-only
license-file: LICENSE
author: __USER-NAME__
maintainer: __USER-MAIL-ADDRESS__
-- copyright:
category: Language
build-type: Simple
extra-doc-files:
common common
ghc-options: -Wno-typed-holes -fdefer-typed-holes
default-extensions:
BlockArguments
DataKinds
DeriveDataTypeable
DeriveGeneric
DeriveTraversable
DerivingVia
FlexibleContexts
GADTs
GeneralisedNewtypeDeriving
LambdaCase
MultiWayIf
NoFieldSelectors
OverloadedLabels
OverloadedRecordDot
OverloadedStrings
PartialTypeSignatures
PatternSynonyms
StandaloneDeriving
TypeApplications
TypeFamilies
default-language: GHC2021
library
import: common
-- cabal-fmt: expand sydml/src/ -Main
exposed-modules:
default-language: GHC2021
build-depends:
, base ^>=4.19.1.0
, containers
, hashable
, mtl
, lens
, pretty-simple
, text >=2.0 && <2.2
, transformers
, unordered-containers
hs-source-dirs: src

View File

@@ -0,0 +1,36 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, ... }@inputs:
inputs.flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
hlib = pkgs.haskell.lib.compose;
hpkgs = pkgs.haskell.packages.ghc98.extend (final: prev: {
__PROJECT-NAME__ =
hlib.dontCheck (final.callCabal2nix "__PROJECT-NAME__" ./. {});
});
in {
packages = rec {
__PROJECT-NAME__ = hpkgs.__PROJECT-NAME__;
default = __PROJECT-NAME__;
};
devShells.default = hpkgs.shellFor {
packages = p: [
p.__PROJECT-NAME__
];
nativeBuildInputs = [
hpkgs.cabal-fmt
hpkgs.fourmolu
hpkgs.haskell-language-server
hpkgs.cabal-install
hpkgs.hasktags
];
withHoogle = true;
};
});
}

View File

@@ -1,6 +1,7 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
# TODO: Move somewhere else.
my-email = "lomiskiam@gmail.com"; my-email = "lomiskiam@gmail.com";
my-name = "Madeleine Sydney"; my-name = "Madeleine Sydney";
in lib.mkMerge [ in lib.mkMerge [

View File

@@ -0,0 +1,46 @@
{ config, lib, pkgs, ... }:
{
# Convenient shorthand for quickly opening Haskell REPLs.
programs.bash.profileExtra = ''
# Start a GHCi REPL with the given packages made available.
ghci-with-packages () {
nix-shell -p "haskellPackages.ghcWithPackages (p: with p; [ $@ ])" \
--run ghci
}
# Run GHC with the given packages made available.
ghc-with-packages () {
getopt -o "p" -- "$@"
while true; do
case "$1" in
-p)
packages="$1"
shift 2
;;
--)
shift
break
;;
esac
done
if [ $? -ne 0 ]; then
echo "Invalid options provided"
exit 1
fi
eval set -- "$options"
nix-shell -p "haskellPackages.ghcWithPackages (p: with p; [ $packages ])" \
--run "ghc $@"
}
'';
# Some global Cabal configuration.
xdg.configFile.".cabal/config".text = ''
-- Globally-enable Nix integration. See
-- https://cabal.readthedocs.io/en/3.4/nix-integration.html
nix: True
'';
}