diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d2e0a43..4b92963e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,12 +23,14 @@ jobs: - 28.1 - 28.2 - 29.1 + - 29.4 + - 30.1 - snapshot include: - - emacs_version: 29.1 + - emacs_version: 29.4 target: deploy-manual steps: - - uses: cachix/install-nix-action@V27 + - uses: cachix/install-nix-action@v31 with: nix_path: nixpkgs=channel:nixos-unstable - uses: purcell/setup-emacs@master diff --git a/haskell-c2hs.el b/haskell-c2hs.el index 8eb31ff8..092f4753 100644 --- a/haskell-c2hs.el +++ b/haskell-c2hs.el @@ -34,12 +34,12 @@ (add-to-list 'auto-mode-alist '("\\.chs\\'" . haskell-c2hs-mode)) (defface haskell-c2hs-hook-pair-face - '((t (:inherit 'font-lock-preprocessor-face))) + '((t (:inherit font-lock-preprocessor-face))) "Face for highlighting {#...#} pairs." :group 'haskell) (defface haskell-c2hs-hook-name-face - '((t (:inherit 'font-lock-keyword-face))) + '((t (:inherit font-lock-keyword-face))) "Face for highlighting c2hs hook names." :group 'haskell) diff --git a/haskell-cabal.el b/haskell-cabal.el index c0707009..f37a3702 100644 --- a/haskell-cabal.el +++ b/haskell-cabal.el @@ -174,8 +174,8 @@ it from list if one of the following conditions are hold: (add-to-list 'haskell-cabal-buffers (current-buffer)) (add-hook 'change-major-mode-hook 'haskell-cabal-unregister-buffer nil 'local) (add-hook 'kill-buffer-hook 'haskell-cabal-unregister-buffer nil 'local) - (setq-local comment-start "-- ") - (setq-local comment-start-skip "\\(^[ \t]*\\)--[ \t]*") + (setq-local comment-start "--") + (setq-local comment-start-skip "--[ \t]*") (setq-local comment-end "") (setq-local comment-end-skip "[ \t]*\\(\\s>\\|\n\\)") (setq-local indent-line-function 'haskell-cabal-indent-line) @@ -474,8 +474,20 @@ Possible results are \\='section-header \\='subsection-header \\='section-data :end (save-match-data (haskell-cabal-subsection-end)) :data-start-column (save-excursion (goto-char (match-end 0)) (current-column)) + ;; Note: Redundant leading commas are allowed since Cabal 2.2. + ;; Example: + ;; build-depends: + ;; , base + ;; , text + ;; + ;; Note: More than one newlines are allowed after the subsection name. + ;; Example: + ;; build-depends: + ;; + ;; + ;; base :data-indent-column (save-excursion (goto-char (match-end 0)) - (when (looking-at "\n +\\(\\w*\\)") (goto-char (match-beginning 1))) + (when (looking-at "\n[\n\t ]* +\\([\\w,]*\\)") (goto-char (match-beginning 1))) (current-column) ))))) @@ -1058,11 +1070,24 @@ Source names from main-is and c-sources sections are left untouched (cl-case (haskell-cabal-classify-line) (section-data (save-excursion - (let ((indent (haskell-cabal-section-data-indent-column - (haskell-cabal-subsection)))) - (indent-line-to indent) + (let* ((subsection (haskell-cabal-subsection)) + (beginning (haskell-cabal-section-start subsection)) + (indent-column (haskell-cabal-section-data-indent-column subsection)) + (subsection-leading-comma-p (save-excursion + (goto-char beginning) + (looking-at ",\\|\n[ \t\n]*,")))) + (indent-line-to indent-column) (beginning-of-line) - (when (looking-at "[ ]*\\([ ]\\{2\\},[ ]*\\)") + ;; Only do extra adjustment if the first item is not comma leading. + ;; Example of the two cases: + ;; + ;; | aaa | aaa + ;; | , bbb | , bbb + ;; + ;; | , aaa | , aaa + ;; | , bbb | , bbb + (when (and (not subsection-leading-comma-p) + (looking-at "[ ]*\\([ ]\\{2\\},[ ]*\\)")) (replace-match ", " t t nil 1))))) (empty (indent-relative))) diff --git a/haskell-commands.el b/haskell-commands.el index f9ed3da4..dd6fb99c 100644 --- a/haskell-commands.el +++ b/haskell-commands.el @@ -46,7 +46,7 @@ (defcustom haskell-mode-stylish-haskell-args nil "Arguments to pass to program specified by haskell-mode-stylish-haskell-path." :group 'haskell - :type 'list) + :type '(list string)) (defcustom haskell-interactive-set-+c t @@ -710,9 +710,9 @@ function `xref-find-definitions' after new table was generated." (haskell-mode-message-line "Tags generated.")))))) (defun haskell-process-add-cabal-autogen () - "Add cabal's autogen dir to the GHCi search path. + "Add the cabal autogen dir to the GHCi search path. Add /dist/build/autogen/ to GHCi seatch path. -This allows modules such as 'Path_...', generated by cabal, to be +This allows modules such as \"Path_...\", generated by cabal, to be loaded by GHCi." (unless (eq 'cabal-repl (haskell-process-type)) (let* @@ -769,9 +769,8 @@ inferior GHCi process." (haskell-session-set-target session target) (when (not (string= old-target target)) (haskell-mode-toggle-interactive-prompt-state) - (unwind-protect - (when (y-or-n-p "Target changed, restart haskell process? ") - (haskell-process-start session))) + (when (y-or-n-p "Target changed, restart haskell process? ") + (haskell-process-start session)) (haskell-mode-toggle-interactive-prompt-state t))))) ;;;###autoload diff --git a/haskell-customize.el b/haskell-customize.el index dccddc7a..3ed41aff 100644 --- a/haskell-customize.el +++ b/haskell-customize.el @@ -394,7 +394,7 @@ hindent, structured-haskell-mode, tool-de-jour, etc. You can set this per-project with a .dir-locals.el file" :group 'haskell - :type '(repeat 'string)) + :type '(repeat string)) (defcustom haskell-stylish-on-save nil "Whether to run stylish-haskell on the buffer before saving. diff --git a/haskell-decl-scan.el b/haskell-decl-scan.el index 2f4ddff2..8299f34e 100644 --- a/haskell-decl-scan.el +++ b/haskell-decl-scan.el @@ -106,6 +106,7 @@ (require 'haskell-mode) (require 'syntax) (require 'imenu) +(require 'subr-x) (defgroup haskell-decl-scan nil "Haskell declaration scanning (`imenu' support)." @@ -123,6 +124,11 @@ :group 'haskell-decl-scan :type 'boolean) +(defcustom haskell-decl-scan-sort-imenu t + "Whether to sort the candidates in imenu." + :group 'haskell-decl-scan + :type 'boolean) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; General declaration scanning functions. @@ -537,11 +543,7 @@ datatypes) in a Haskell file for the `imenu' package." ;; These lists are nested using `(INDEX-TITLE . INDEX-ALIST)'. (let* ((bird-literate (haskell-ds-bird-p)) (index-alist '()) - (index-class-alist '()) ;; Classes - (index-var-alist '()) ;; Variables - (index-imp-alist '()) ;; Imports - (index-inst-alist '()) ;; Instances - (index-type-alist '()) ;; Datatypes + (imenu (make-hash-table :test 'equal)) ;; The result we wish to return. result) (goto-char (point-min)) @@ -557,47 +559,29 @@ datatypes) in a Haskell file for the `imenu' package." (posns (cdr name-posns)) (start-pos (car posns)) (type (cdr result))) - ;; Place `(name . start-pos)' in the correct alist. - (cl-case type - (variable - (setq index-var-alist - (cl-acons name start-pos index-var-alist))) - (datatype - (setq index-type-alist - (cl-acons name start-pos index-type-alist))) - (class - (setq index-class-alist - (cl-acons name start-pos index-class-alist))) - (import - (setq index-imp-alist - (cl-acons name start-pos index-imp-alist))) - (instance - (setq index-inst-alist - (cl-acons name start-pos index-inst-alist))))))) + (puthash type + (cons (cons name start-pos) (gethash type imenu '())) + imenu)))) ;; Now sort all the lists, label them, and place them in one list. - (when index-type-alist - (push (cons "Datatypes" - (sort index-type-alist 'haskell-ds-imenu-label-cmp)) - index-alist)) - (when index-inst-alist - (push (cons "Instances" - (sort index-inst-alist 'haskell-ds-imenu-label-cmp)) - index-alist)) - (when index-imp-alist - (push (cons "Imports" - (sort index-imp-alist 'haskell-ds-imenu-label-cmp)) - index-alist)) - (when index-class-alist - (push (cons "Classes" - (sort index-class-alist 'haskell-ds-imenu-label-cmp)) - index-alist)) - (when index-var-alist + (dolist (type '((datatype . "Datatypes") (instance . "Instances") + (import . "Imports") (class . "Classes"))) + (when-let ((curr-alist (gethash (car type) imenu))) + (push (cons (cdr type) + (if haskell-decl-scan-sort-imenu + (sort curr-alist 'haskell-ds-imenu-label-cmp) + (reverse curr-alist))) + index-alist))) + (when-let ((var-alist (gethash 'variable imenu))) (if haskell-decl-scan-bindings-as-variables (push (cons "Variables" - (sort index-var-alist 'haskell-ds-imenu-label-cmp)) + (if haskell-decl-scan-sort-imenu + (sort var-alist 'haskell-ds-imenu-label-cmp) + (reverse var-alist))) index-alist) (setq index-alist (append index-alist - (sort index-var-alist 'haskell-ds-imenu-label-cmp))))) + (if haskell-decl-scan-sort-imenu + (sort var-alist 'haskell-ds-imenu-label-cmp) + (reverse var-alist)))))) ;; Return the alist. index-alist)) diff --git a/haskell-ghc-support.el b/haskell-ghc-support.el index 3a72fc82..3dabc5c5 100644 --- a/haskell-ghc-support.el +++ b/haskell-ghc-support.el @@ -103,7 +103,9 @@ "MonomorphismRestriction" "MultiParamTypeClasses" "MultiWayIf" + "MultilineStrings" "NPlusKPatterns" + "NamedDefaults" "NamedFieldPuns" "NamedWildCards" "NegativeLiterals" @@ -179,7 +181,9 @@ "NoMonomorphismRestriction" "NoMultiParamTypeClasses" "NoMultiWayIf" + "NoMultilineStrings" "NoNPlusKPatterns" + "NoNamedDefaults" "NoNamedFieldPuns" "NoNamedWildCards" "NoNegativeLiterals" @@ -187,6 +191,7 @@ "NoNullaryTypeClasses" "NoNumDecimals" "NoNumericUnderscores" + "NoOrPatterns" "NoOverlappingInstances" "NoOverloadedLabels" "NoOverloadedLists" @@ -249,6 +254,7 @@ "NullaryTypeClasses" "NumDecimals" "NumericUnderscores" + "OrPatterns" "OverlappingInstances" "OverloadedLabels" "OverloadedLists" @@ -524,6 +530,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-Werror=unused-record-wildcards" "-Werror=unused-top-binds" "-Werror=unused-type-patterns" + "-Werror=view-pattern-signatures" "-Werror=warnings-deprecations" "-Werror=wrong-do-bind" "-Weverything" @@ -720,6 +727,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-Wno-error=unused-record-wildcards" "-Wno-error=unused-top-binds" "-Wno-error=unused-type-patterns" + "-Wno-error=view-pattern-signatures" "-Wno-error=warnings-deprecations" "-Wno-error=wrong-do-bind" "-Wno-everything" @@ -815,6 +823,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-Wno-unused-record-wildcards" "-Wno-unused-top-binds" "-Wno-unused-type-patterns" + "-Wno-view-pattern-signatures" "-Wno-warnings-deprecations" "-Wno-wrong-do-bind" "-Wnoncanonical-monad-instances" @@ -864,6 +873,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-Wunused-record-wildcards" "-Wunused-top-binds" "-Wunused-type-patterns" + "-Wview-pattern-signatures" "-Wwarn" "-Wwarn=all" "-Wwarn=all-missed-specialisations" @@ -984,6 +994,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-Wwarn=unused-record-wildcards" "-Wwarn=unused-top-binds" "-Wwarn=unused-type-patterns" + "-Wwarn=view-pattern-signatures" "-Wwarn=warnings-deprecations" "-Wwarn=wrong-do-bind" "-Wwarnings-deprecations" @@ -1065,7 +1076,9 @@ This list should be updated by running `haskell-update-ghc-support'.") "-XMonomorphismRestriction" "-XMultiParamTypeClasses" "-XMultiWayIf" + "-XMultilineStrings" "-XNPlusKPatterns" + "-XNamedDefaults" "-XNamedFieldPuns" "-XNamedWildCards" "-XNegativeLiterals" @@ -1142,7 +1155,9 @@ This list should be updated by running `haskell-update-ghc-support'.") "-XNoMonomorphismRestriction" "-XNoMultiParamTypeClasses" "-XNoMultiWayIf" + "-XNoMultilineStrings" "-XNoNPlusKPatterns" + "-XNoNamedDefaults" "-XNoNamedFieldPuns" "-XNoNamedWildCards" "-XNoNegativeLiterals" @@ -1150,6 +1165,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-XNoNullaryTypeClasses" "-XNoNumDecimals" "-XNoNumericUnderscores" + "-XNoOrPatterns" "-XNoOverlappingInstances" "-XNoOverloadedLabels" "-XNoOverloadedLists" @@ -1212,6 +1228,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-XNullaryTypeClasses" "-XNumDecimals" "-XNumericUnderscores" + "-XOrPatterns" "-XOverlappingInstances" "-XOverloadedLabels" "-XOverloadedLists" @@ -1537,6 +1554,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-fexitification" "-fexpose-all-unfoldings" "-fexpose-internal-symbols" + "-fexpose-overloaded-unfoldings" "-fext-core" "-fextended-default-rules" "-fexternal-dynamic-refs" @@ -1592,6 +1610,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-flocal-float-out-top-level" "-floopification" "-fmax-errors" + "-fmax-forced-spec-args" "-fmax-inline-alloc-size" "-fmax-inline-memcpy-insns" "-fmax-inline-memset-insns" @@ -1662,6 +1681,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-fno-exitification" "-fno-expose-all-unfoldings" "-fno-expose-internal-symbols" + "-fno-expose-overloaded-unfoldings" "-fno-ext-core" "-fno-extended-default-rules" "-fno-external-dynamic-refs" @@ -1716,6 +1736,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-fno-mono-pat-binds" "-fno-monomorphism-restriction" "-fno-num-constant-folding" + "-fno-object-determinism" "-fno-omit-interface-pragmas" "-fno-omit-yields" "-fno-opt-coercion" @@ -1855,6 +1876,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-fno-write-interface" "-fnum-constant-folding" "-fobject-code" + "-fobject-determinism" "-fomit-interface-pragmas" "-fomit-yields" "-foptimal-applicative-do" @@ -2023,6 +2045,7 @@ This list should be updated by running `haskell-update-ghc-support'.") "-fworker-wrapper" "-fworker-wrapper-cbv" "-fwrite-ide-info" + "-fwrite-if-compression" "-fwrite-if-simplified-core" "-fwrite-interface" "-g" @@ -2100,7 +2123,9 @@ This list should be updated by running `haskell-update-ghc-support'.") "-o" "-odir" "-ohi" + "-optCmmP" "-optF" + "-optJSP" "-optL" "-optP" "-opta" @@ -2123,7 +2148,9 @@ This list should be updated by running `haskell-update-ghc-support'.") "-package-key" "-package-name" "-parallel" + "-pgmCmmP" "-pgmF" + "-pgmJSP" "-pgmL" "-pgmP" "-pgma" diff --git a/haskell-indentation.el b/haskell-indentation.el index 4c252512..1576741b 100644 --- a/haskell-indentation.el +++ b/haskell-indentation.el @@ -1135,8 +1135,7 @@ layout starts." (haskell-indentation-add-indentation (+ left-indent haskell-indentation-starter-offset)) (throw 'parse-end nil)) - (setq phrase1 (cddr phrase))) - ((string= (cadr phrase) "in")))))) + (setq phrase1 (cddr phrase))))))) (defun haskell-indentation-add-indentation (indent) "" ; FIXME diff --git a/haskell-load.el b/haskell-load.el index 48e75e2e..29804a56 100644 --- a/haskell-load.el +++ b/haskell-load.el @@ -252,7 +252,7 @@ list of modules where missed IDENT was found." (haskell-process-send-string (cadr state) (format haskell-process-do-cabal-format-string - (haskell-session-cabal-dir (car state)) + (shell-quote-argument (haskell-session-cabal-dir (car state))) (format "%s %s" app-name (cl-caddr state))))) :live (lambda (state buffer) diff --git a/haskell-mode.el b/haskell-mode.el index f8e7c3fb..bae5fb44 100644 --- a/haskell-mode.el +++ b/haskell-mode.el @@ -854,7 +854,7 @@ Minor modes that work well with `haskell-mode': (setq-local dabbrev-case-fold-search nil) (setq-local dabbrev-case-distinction nil) (setq-local dabbrev-case-replace nil) - (setq-local dabbrev-abbrev-char-regexp "\\sw\\|[.]") + (setq-local dabbrev-abbrev-char-regexp "\\sw\\|\\s_\\|[.]") (setq haskell-literate nil) (add-hook 'before-save-hook 'haskell-mode-before-save-handler nil t) (add-hook 'after-save-hook 'haskell-mode-after-save-handler nil t) diff --git a/haskell-repl.el b/haskell-repl.el index 58be6a23..8ce30dfe 100644 --- a/haskell-repl.el +++ b/haskell-repl.el @@ -70,34 +70,35 @@ "Run the given expression." (let ((session (haskell-interactive-session)) (process (haskell-interactive-process))) - (haskell-process-queue-command - process - (make-haskell-command - :state (list session process expr 0) - :go (lambda (state) - (goto-char (point-max)) - (insert "\n") - (setq haskell-interactive-mode-result-end - (point-max)) - (haskell-process-send-string (cadr state) - (haskell-interactive-mode-multi-line (cl-caddr state))) - (haskell-process-set-evaluating (cadr state) t)) - :live (lambda (state buffer) - (unless (and (string-prefix-p ":q" (cl-caddr state)) - (string-prefix-p (cl-caddr state) ":quit")) - (let* ((cursor (cl-cadddr state)) - (next (replace-regexp-in-string - haskell-process-prompt-regex - "" - (substring buffer cursor)))) - (haskell-interactive-mode-eval-result (car state) next) - (setf (cl-cdddr state) (list (length buffer))) - nil))) - :complete - (lambda (state response) - (haskell-process-set-evaluating (cadr state) nil) - (unless (haskell-interactive-mode-trigger-compile-error state response) - (haskell-interactive-mode-expr-result state response))))))) + (with-current-buffer (haskell-session-interactive-buffer session) + (haskell-process-queue-command + process + (make-haskell-command + :state (list session process expr 0) + :go (lambda (state) + (goto-char (point-max)) + (insert "\n") + (setq haskell-interactive-mode-result-end + (point-max)) + (haskell-process-send-string (cadr state) + (haskell-interactive-mode-multi-line (cl-caddr state))) + (haskell-process-set-evaluating (cadr state) t)) + :live (lambda (state buffer) + (unless (and (string-prefix-p ":q" (cl-caddr state)) + (string-prefix-p (cl-caddr state) ":quit")) + (let* ((cursor (cl-cadddr state)) + (next (replace-regexp-in-string + haskell-process-prompt-regex + "" + (substring buffer cursor)))) + (haskell-interactive-mode-eval-result (car state) next) + (setf (cl-cdddr state) (list (length buffer))) + nil))) + :complete + (lambda (state response) + (haskell-process-set-evaluating (cadr state) nil) + (unless (haskell-interactive-mode-trigger-compile-error state response) + (haskell-interactive-mode-expr-result state response)))))))) (defun haskell-interactive-mode-expr-result (state response) "Print the result of evaluating the expression." diff --git a/haskell.el b/haskell.el index 1b358051..33e7ac7b 100644 --- a/haskell.el +++ b/haskell.el @@ -38,6 +38,7 @@ (defvar interactive-haskell-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c C-l") 'haskell-process-load-file) + (define-key map (kbd "C-c RET") 'haskell-load-and-run) ;; == C-c C-m (define-key map (kbd "C-c C-r") 'haskell-process-reload) (define-key map (kbd "C-c C-t") 'haskell-process-do-type) (define-key map (kbd "C-c C-i") 'haskell-process-do-info) @@ -166,11 +167,10 @@ (interactive) (when (eq major-mode 'haskell-interactive-mode) (haskell-mode-toggle-interactive-prompt-state) - (unwind-protect - (when (and (boundp 'haskell-session) - haskell-session - (y-or-n-p "Kill the whole session? ")) - (haskell-session-kill t))) + (when (and (boundp 'haskell-session) + haskell-session + (y-or-n-p "Kill the whole session? ")) + (haskell-session-kill t)) (haskell-mode-toggle-interactive-prompt-state t))) (defun haskell-session-make (name) @@ -222,13 +222,12 @@ If `haskell-process-load-or-reload-prompt' is nil, accept `default'." (when (not (string= name "")) (let ((session (haskell-session-lookup name))) (haskell-mode-toggle-interactive-prompt-state) - (unwind-protect - (if session - (when - (y-or-n-p - (format "Session %s already exists. Use it?" name)) - session) - (haskell-session-make name))) + (if session + (when + (y-or-n-p + (format "Session %s already exists. Use it?" name)) + session) + (haskell-session-make name)) (haskell-mode-toggle-interactive-prompt-state t))))) ;;;###autoload @@ -397,6 +396,17 @@ Give optional NEXT-P parameter to override value of nil (current-buffer))) +(defvar haskell-load-and-run-expr-history nil + "History of expressions used in `haskell-load-and-run'.") + +(defun haskell-load-and-run (expr) + "Load the current buffer and run EXPR, e.g. \"main\"." + (interactive (list (read-string "Run expression: " + (car haskell-load-and-run-expr-history) + 'haskell-load-and-run-expr-history))) + (haskell-process-load-file) + (haskell-interactive-mode-run-expr expr)) + ;;;###autoload (defun haskell-process-reload () "Re-load the current buffer file." diff --git a/w3m-haddock.el b/w3m-haddock.el index 0f6a9e11..59b118bf 100644 --- a/w3m-haddock.el +++ b/w3m-haddock.el @@ -54,7 +54,7 @@ You can rebind this if you're using hsenv by adding it to your " :group 'haskell - :type 'list) + :type '(list string)) (defvar w3m-haddock-entry-regex "^\\(\\(data\\|type\\) \\|[a-z].* :: \\)" "Regex to match entry headings.")