This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,7 @@
(install
(package dune)
(section share_root)
(files
(dune.el as emacs/site-lisp/dune.el)
(dune-flymake.el as emacs/site-lisp/dune-flymake.el)
(dune-watch.el as emacs/site-lisp/dune-watch.el)))

View file

@ -0,0 +1,208 @@
;;; dune-flymake.el --- Flymake support for dune files -*- coding: utf-8 -*-
;; Copyright 2017- Christophe Troestler
;; URL: https://github.com/ocaml/dune
;; Version: 1.0
;; This file is not part of GNU Emacs.
;;; Commentary:
;; This package complements the dune mode with on the fly tests to
;; pinpoint errors.
;; Permission to use, copy, modify, and distribute this software for
;; any purpose with or without fee is hereby granted, provided that
;; the above copyright notice and this permission notice appear in
;; all copies.
;;
;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
;; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
(require 'flymake)
(require 'dune)
;;; Code:
(defvar dune-flymake-temporary-file-directory
(expand-file-name "dune" temporary-file-directory)
"Directory where to duplicate the files for flymake.")
(defvar dune-flymake-program
(expand-file-name "dune-lint" dune-flymake-temporary-file-directory)
"Script to use to check the dune file.")
(defvar dune-flymake--allowed-file-name-masks
'("\\(?:\\`\\|/\\)dune\\'" dune-flymake-init
dune-flymake-cleanup)
"Flymake entry for dune files. See `flymake-allowed-file-name-masks'.")
(defvar dune-flymake--err-line-patterns
;; Beware that the path from the root will be reported by dune
;; but flymake requires it to match the file name.
'(("File \"[^\"]*\\(dune\\)\", line \\([0-9]+\\), \
characters \\([0-9]+\\)-\\([0-9]+\\): +\\([^\n]*\\)$"
1 2 3 5))
"Value of `flymake-err-line-patterns' for dune files.")
(defun dune-flymake-create-lint-script ()
"Create the lint script if it does not exist.
This is needed as long as https://github.com/ocaml/dune/issues/241
is not fixed."
(unless (file-exists-p dune-flymake-program)
(let ((dir (file-name-directory dune-program))
(pgm "#!/usr/bin/env ocaml
;;
#load \"unix.cma\";;
#load \"str.cma\";;
open Printf
let filename = Sys.argv.(1)
let root = try Some(Sys.argv.(2)) with _ -> None
let read_all fh =
let buf = Buffer.create 1024 in
let b = Bytes.create 1024 in
let len = ref 0 in
while len := input fh b 0 1024; !len > 0 do
Buffer.add_subbytes buf b 0 !len
done;
Buffer.contents buf
let errors =
let root = match root with
| None | Some \"\" -> \"\"
| Some r -> \"--root=\" ^ Filename.quote r in
let cmd = sprintf \"dune external-lib-deps %s %s\" root
(Filename.quote (Filename.basename filename)) in
let env = Unix.environment() in
let (_,_,fh) as p = Unix.open_process_full cmd env in
let out = read_all fh in
match Unix.close_process_full p with
| Unix.WEXITED (0|1) ->
(* dune will normally exit with 1 as it will not be able to
perform the requested action. *)
out
| Unix.WEXITED 127 -> printf \"dune not found in path.\\n\"; exit 1
| Unix.WEXITED n -> printf \"dune exited with status %d.\\n\" n; exit 1
| Unix.WSIGNALED n -> printf \"dune was killed by signal %d.\\n\" n;
exit 1
| Unix.WSTOPPED n -> printf \"dune was stopped by signal %d\\n.\" n;
exit 1
let () =
let re = \"\\\\(:?\\\\)[\\r\\n]+\\\\([a-zA-Z]+\\\\)\" in
let errors = Str.global_substitute (Str.regexp re)
(fun s -> let colon = Str.matched_group 1 s = \":\" in
let f = Str.matched_group 2 s in
if f = \"File\" then \"\\n File\"
else if colon then \": \" ^ f
else \", \" ^ f)
errors in
print_string errors"))
(make-directory dir t)
(append-to-file pgm nil dune-program)
(set-file-modes dune-program #o777)
)))
(defun dune-flymake--temp-name (absolute-path)
"Return the full path of the copy of ABSOLUTE-PATH in the temp dir.
The temporary directory is given by `dune-flymake-temporary-file-directory'."
(let ((slash-pos (string-match "/" absolute-path)))
(file-truename (expand-file-name (substring absolute-path (1+ slash-pos))
dune-flymake-temporary-file-directory))))
(defun dune-flymake--opam-files (dir)
"Return all opam files in the directory DIR."
(let ((files nil))
(dolist (f (directory-files-and-attributes dir t ".*\\.opam\\'"))
(when (null (cadr f))
(push (car f) files)))
files))
(defun dune-flymake--root (filename)
"Return the Dune root for FILENAME.
Create the temporary copy the necessary context files for dune."
;; FIXME: the root depends on dune-project. If none is found,
;; assume the commands are issued from the dir where opam files are found.
(let* ((dir (locate-dominating-file (file-name-directory filename)
#'dune-flymake--opam-files)))
(when dir
(setq dir (expand-file-name dir)); In case it is ~/...
(make-directory (dune-flymake--temp-name dir) t)
(dolist (f (dune-flymake--opam-files dir))
(copy-file f (dune-flymake--temp-name f) t)))
dir))
(defalias 'dune-flymake--safe-delete-file
(if (fboundp 'flymake-proc--safe-delete-file)
'flymake-proc--safe-delete-file
'flymake-safe-delete-file))
(defun dune-flymake--delete-opam-files (dir)
"Delete all opam files in the directory DIR."
(dolist (f (dune-flymake--opam-files dir))
(dune-flymake--safe-delete-file f)))
(defvaralias 'dune-flymake--temp-source-file-name
(if (boundp 'flymake-proc--temp-source-file-name)
'flymake-proc--temp-source-file-name
'flymake-temp-source-file-name))
(defun dune-flymake-cleanup ()
"Attempt to delete temp dir created by `dune-flymake-create-temp'.
Do not fail on error."
(let ((dir (file-name-directory dune-flymake--temp-source-file-name))
(temp-dir (concat (directory-file-name
dune-flymake-temporary-file-directory) "/")))
(flymake-log 3 "Clean up %s" dune-flymake--temp-source-file-name)
(dune-flymake--safe-delete-file dune-flymake--temp-source-file-name)
(condition-case nil
(delete-directory (expand-file-name "_build" dir) t)
(error nil))
;; Also delete parent dirs if empty or only contain opam files
(while (and (not (string-equal dir temp-dir))
(> (length dir) 0))
(condition-case nil
(progn
(dune-flymake--delete-opam-files dir)
(delete-directory dir)
(setq dir (file-name-directory (directory-file-name dir))))
(error ; then top the loop
(setq dir ""))))))
(defalias 'dune-flymake--create-temp-buffer-copy
(if (fboundp 'flymake-proc-init-create-temp-buffer-copy)
'flymake-proc-init-create-temp-buffer-copy
'flymake-init-create-temp-buffer-copy))
(defun dune-flymake-init ()
"Set up dune-flymake."
(dune-flymake-create-lint-script)
(let ((fname (dune-flymake--create-temp-buffer-copy
'dune-flymake-create-temp))
(root (or (dune-flymake--root buffer-file-name) "")))
(list dune-program (list fname root))))
(defun dune-flymake-dune-mode-hook ()
"Hook to add to `dune-mode-hook' to enable lint tests."
(push dune-flymake--allowed-file-name-masks
(if (boundp 'flymake-proc-allowed-file-name-masks)
flymake-proc-allowed-file-name-masks
flymake-allowed-file-name-masks))
(set (make-local-variable (if (boundp 'flymake-proc-err-line-patterns)
'flymake-proc-err-line-patterns
'flymake-err-line-patterns))
dune-flymake--err-line-patterns))
(provide 'dune-flymake)
;;; dune-flymake.el ends here

View file

@ -0,0 +1,197 @@
;;; dune-watch.el --- provides integration with dune watch -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Kiran Gopinathan
;; Author: Kiran Gopinathan <kirang@comp.nus.edu.sg>
;; Keywords:
;; This file is not part of GNU Emacs.
;;; Commentary:
;; This package complements dune mode with integration with --watch
;; tasks.
;; Permission to use, copy, modify, and distribute this software for
;; any purpose with or without fee is hereby granted, provided that
;; the above copyright notice and this permission notice appear in
;; all copies.
;;
;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
;; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
;;; Code:
(require 'subr-x)
;;;; Customisation
(defgroup dune-watch nil
"Customisation group for dune-watch-minor-mode."
:prefix "dune-watch-mode-"
:group 'applications)
;;;;; Command configuration
(defcustom dune-watch-default-command "build"
"Default command for dune-watch."
:type 'string
:group 'dune-watch)
(defcustom dune-watch-command-format
"opam exec -- dune %s --watch"
"Format of command to run to invoke dune watch mode."
:type 'string
:group 'dune-watch)
(defcustom dune-watch-delete-buffer-on-termination t
"Whether to delete the dune watch buffer when the dune watch process terminates."
:type 'bool
:group 'dune-watch)
(defcustom dune-watch-popup-function #'display-buffer-pop-up-window
"Function passed to `display-buffer' to present compilation output to user."
:type 'function
:group 'dune-watch)
(defcustom dune-watch-read-command t
"Whether the user should be prompted to select a build task."
:type 'bool
:group 'dune-watch)
;;;; Constants
(defconst dune-watch-buffer-name "*dune-watch*"
"Name of buffer used to track buffer outputs.")
(defconst dune-watch-process-name "*dune-watch-process*"
"Name of process used to run dune watch.")
(defconst dune-watch-header "********** NEW BUILD"
"Prefix of the header of dune watch output.")
(defconst dune-watch-read-prompt "dune task (default: %s): "
"Prompt displayed to the user to dune task.")
(defconst dune-watch-supported-commands '("build" "test")
"List of dune build tasks supported by dune-watch.")
;;;; Global variables
(defvar dune-watch-task-history nil
"Variable used to track history of dune build tasks.")
;;;; Local variables
(defvar-local dune-watch-buffer nil
"Buffer corresponding to dune watch for current program")
(defvar-local dune-watch-process nil
"Process corresponding to dune watch for current program")
(defvar-local dune-watch-header-start nil
"Start of the header of the most recent dune watch output.")
;;;; Implementation
(defun dune-watch-generate-new-buffer ()
"Return a new buffer to be used by dune watch."
(let ((buffer (generate-new-buffer dune-watch-buffer-name)))
(with-current-buffer buffer
(compilation-mode)
(setq dune-watch-buffer buffer)
(setq dune-watch-process nil)
(setq dune-watch-header-start nil))
buffer))
(defun dune-watch-sentinel-function (watch-buffer process event)
"Process sentinel used by dune-watch-minor-mode.
WATCH-BUFFER is the buffer used by dune watch.
PROCESS is the dune-watch process name
EVENT is the text output by the sentinel."
(when (and dune-watch-delete-buffer-on-termination (buffer-live-p watch-buffer))
(kill-buffer watch-buffer))
(message "Dune watch process %s terminated with message \"%s\"" process (string-trim event)))
(defun dune-watch-update-header-start ()
"Update the position of the header of the most recent dune watch output."
(save-excursion
(goto-char (point-max))
(setq dune-watch-header-start
(search-backward dune-watch-header nil t))))
(defun dune-watch-beautify-buffer ()
"Cleans dune watch buffer by removing all but the last build output."
(let ((inhibit-read-only t)
(buffer-start (point-min)))
(when (and dune-watch-header-start buffer-start)
(delete-region buffer-start dune-watch-header-start)
(setq dune-watch-header-start buffer-start))))
(defun dune-watch-contains-errors ()
"Determines whether the dune watch buffer output contain any errors."
(save-excursion
(when dune-watch-header-start
(goto-char dune-watch-header-start)
(search-forward "File" nil t))))
(defun dune-watch-popup-buffer ()
"Pops up compilation output to user."
(when dune-watch-buffer
(display-buffer dune-watch-buffer dune-watch-popup-function)))
(defun dune-watch-filter-function (watch-buffer process event)
"Process filter function used by dune watch.
WATCH-BUFFER is the buffer corresponding to the process.
PROCESS is the name of the process.
EVENT is the string returned by the dune watch."
(when (and watch-buffer (buffer-live-p watch-buffer))
(with-current-buffer watch-buffer
(let ((inhibit-read-only t)
(buffer-end (point-max)))
(goto-char buffer-end)
(insert event)
(dune-watch-update-header-start)
(dune-watch-beautify-buffer)
(when (dune-watch-contains-errors)
(dune-watch-popup-buffer))))))
(defun dune-watch-on-kill-buffer ()
"Kill dune-watch process when main buffer is killed."
(when (and dune-watch-minor-mode dune-watch-process (process-live-p dune-watch-process))
(kill-process dune-watch-process)))
(defun start-dune-watch (command)
"Start a subprocess to run the dune COMMAND using the --watch flag."
(message "starting process to watch %s task..." command)
(let* ((buffer (dune-watch-generate-new-buffer))
(filter-process (apply-partially #'dune-watch-filter-function buffer))
(sentinel-process (apply-partially #'dune-watch-sentinel-function buffer))
(command (format dune-watch-command-format command)))
(setq dune-watch-buffer buffer)
(setq dune-watch-process (make-process
:name dune-watch-process-name
:buffer buffer
:command (split-string command)
:filter filter-process
:sentinel sentinel-process))
(add-hook 'kill-buffer-hook #'dune-watch-on-kill-buffer)))
(define-minor-mode dune-watch-minor-mode "A minor mode to run dune commands"
:group 'dune-watch
:init-value nil
(let ((command dune-watch-default-command))
(when dune-watch-read-command
(setq command (or (completing-read
(format dune-watch-read-prompt dune-watch-default-command)
dune-watch-supported-commands
nil nil dune-watch-default-command 'dune-watch-task-history)
command)))
(start-dune-watch command)))
(provide 'dune-watch)
;;; dune-watch.el ends here

View file

@ -0,0 +1,463 @@
;;; dune.el --- Integration with the dune build system
;; Copyright 2018 Jane Street Group, LLC <opensource@janestreet.com>
;; 2017- Christophe Troestler
;; URL: https://github.com/ocaml/dune
;; Version: 1.0
;;; Commentary:
;; This package provides helper functions for interacting with the
;; dune build system from Emacs. It also prevides a mode to edit dune
;; files.
;; Installation:
;; You need to install the OCaml program ``dune''. The
;; easiest way to do so is to install the opam package manager:
;;
;; https://opam.ocaml.org/doc/Install.html
;;
;; and then run "opam install dune".
;; This file is not part of GNU Emacs.
;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
;; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
;;; Code:
(defgroup dune nil
"Integration with the dune build system."
:tag "Dune build system."
:version "1.0"
:group 'languages)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Syntax highlighting of dune files
(defface dune-error-face
'((t (:inherit error)))
"Face for errors (e.g. obsolete constructs)."
:group 'dune)
(defvar dune-error-face 'dune-error-face
"Face for errors (e.g. obsolete constructs).")
(defface dune-separator-face
'((t (:inherit default)))
"Face for various kind of separators such as ':'."
:group 'dune)
(defvar dune-separator-face 'dune-separator-face
"Face for various kind of separators such as ':'.")
(defconst dune-stanzas-regex
(eval-when-compile
(concat (regexp-opt
'("library" "executable" "executables" "rule" "toplevel"
"ocamllex" "ocamlyacc" "menhir" "alias" "install"
"copy_files" "copy_files#" "include" "tests" "test" "dirs"
"env" "ignored_subdirs" "include_subdirs" "data_only_dirs"
"documentation" "cinaps" "coqlib" "coq.theory" "coq.pp"
"foreign_library")
) "\\(?:\\_>\\|[[:space:]]\\)"))
"Stanzas in dune files.")
(defconst dune-fields-regex
(eval-when-compile
(regexp-opt
'("name" "public_name" "synopsis" "modules" "libraries" "wrapped"
"preprocess" "preprocessor_deps" "optional" "c_names" "cxx_names"
"foreign_stubs" "foreign_archives" "install_c_headers" "modes"
"no_dynlink" "kind" "ppx_runtime_libraries" "virtual_deps" "js_of_ocaml"
"flags" "ocamlc_flags" "ocamlopt_flags" "library_flags" "c_flags"
"cxx_flags" "c_library_flags" "self_build_stubs_archive" "inline_tests"
"modules_without_implementation" "private_modules"
;; + special_builtin_support
"special_builtin_support" "build_info" "data_module" "api_version"
;; +stdlib
"stdlib" "modules_before_stdlib" "exit_module" "internal_modules"
;; + virtual libraries
"virtual_modules" "implements" "variant" "default_implementation"
"allow_overlapping_dependencies"
;; + for "executable" and "executables":
"package" "link_flags" "link_deps" "names" "public_names" "variants"
"forbidden_libraries"
;; + for "foreign_library" and "foreign_stubs":
"archive_name" "language" "names" "flags" "include_dirs" "extra_deps"
;; + for "rule":
"targets" "action" "deps" "mode" "fallback" "locks"
;; + for "menhir":
"merge_into"
;; + for "cinaps":
"files"
;; + for "alias"
"enabled_if"
;; + for env
"binaries"
;; + for "install"
"section" "files"
;; Coq fields
"theories" "modules_flags" "plugins")
'symbols))
"Field names allowed in dune files.")
(defconst dune-builtin-regex
(eval-when-compile
(concat (regexp-opt
'(;; Linking modes
"byte" "native" "best"
;; modes
"standard" "fallback" "promote" "promote-until-clean"
;; Actions
"run" "chdir" "setenv"
"with-stdout-to" "with-stderr-to" "with-outputs-to"
"ignore-stdout" "ignore-stderr" "ignore-outputs"
"with-stdin-from" "with-exit-codes"
"progn" "echo" "write-file" "cat" "copy" "copy#" "system"
"bash" "diff" "diff?" "cmp"
;; FIXME: "flags" is already a field and we do not have enough
;; context to distinguishing both.
"backend" "generate_runner" "runner_libraries" "flags"
"extends"
;; Dependency specification
"file" "alias" "alias_rec" "glob_files" "files_recursively_in"
"universe" "package" "source_tree" "env_var")
t)
"\\(?:\\_>\\|[[:space:]]\\)"))
"Builtin sub-fields in dune.")
(defconst dune-builtin-labels-regex
(regexp-opt '("standard" "include") 'words)
"Builtin :labels in dune.")
(defvar dune-var-kind-regex
(eval-when-compile
(regexp-opt
'("ocaml-config"
"dep" "exe" "bin" "lib" "libexec" "lib-available"
"version" "read" "read-lines" "read-strings")
'words))
"Optional prefix to variable names.")
(defmacro dune--field-vals (field &rest vals)
"Build a `font-lock-keywords' rule for the dune FIELD accepting values VALS."
`(list (concat "(" ,field "[[:space:]]+" ,(regexp-opt vals t))
1 font-lock-constant-face))
(defvar dune-font-lock-keywords
`((,(concat "(\\(" dune-stanzas-regex "\\)") 1 font-lock-keyword-face)
("([^ ]+ +\\(as\\) +[^ ]+)" 1 font-lock-keyword-face)
(,(concat "(" dune-fields-regex) 1 font-lock-function-name-face)
(,(concat "%{" dune-var-kind-regex " *\\(\\:\\)[^{}:]*\\(\\(?::\\)?\\)")
(1 font-lock-builtin-face)
(2 dune-separator-face)
(3 dune-separator-face))
("%{\\([^{}]*\\)}" 1 font-lock-variable-name-face keep)
(,(concat "\\(:" dune-builtin-labels-regex "\\)[[:space:]()\n]")
1 font-lock-builtin-face)
;; Named dependencies:
("(\\(:[a-zA-Z]+\\)[[:space:]]+" 1 font-lock-variable-name-face)
("\\(true\\|false\\)" 1 font-lock-constant-face)
("(\\(select\\)[[:space:]]+[^[:space:]]+[[:space:]]+\\(from\\)\\>"
(1 font-lock-constant-face)
(2 font-lock-constant-face))
,(eval-when-compile
(dune--field-vals "kind" "normal" "ppx_rewriter" "ppx_deriver"))
,(eval-when-compile
(dune--field-vals "mode" "standard" "fallback" "promote"
"promote-until-clean"))
(,(concat "(" dune-builtin-regex) 1 font-lock-builtin-face)
("(preprocess[[:space:]]+(\\(pps\\)" 1 font-lock-builtin-face)
("(name +\\(runtest\\))" 1 font-lock-builtin-face)
(,(eval-when-compile
(concat "(" (regexp-opt '("fallback") t)))
1 dune-error-face)))
(defvar dune-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?\; "< b" table)
(modify-syntax-entry ?\n "> b" table)
(modify-syntax-entry ?\( "()" table)
(modify-syntax-entry ?\) ")(" table)
(modify-syntax-entry ?\{ "(}" table)
(modify-syntax-entry ?\} "){" table)
(modify-syntax-entry ?\[ "(]" table)
(modify-syntax-entry ?\] ")[" table)
table)
"Dune syntax table.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; SMIE
(require 'smie)
(defvar dune-smie-grammar
(when (fboundp 'smie-prec2->grammar)
(smie-prec2->grammar
(smie-bnf->prec2 '()))))
(defun dune-smie-rules (kind token)
"Rules for `smie-setup'.
See `smie-rules-function' for the meaning of KIND and TOKEN."
(cond
((eq kind :close-all) '(column . 0))
((and (eq kind :after) (equal token ")"))
(save-excursion
(goto-char (cadr (smie-indent--parent)))
(if (looking-at-p dune-stanzas-regex)
'(column . 0)
1)))
((eq kind :before)
(if (smie-rule-parent-p "(")
(save-excursion
(goto-char (cadr (smie-indent--parent)))
(cond
((looking-at-p dune-stanzas-regex) 1)
((looking-at-p dune-fields-regex)
(smie-rule-parent 0))
((smie-rule-sibling-p) (cons 'column (current-column)))
(t (cons 'column (current-column)))))
'(column . 0)))
((eq kind :list-intro)
nil)
(t 1)))
(defun dune-smie-rules-verbose (kind token)
"Same as `dune-smie-rules' but echoing information.
See `smie-rules-function' for the meaning of KIND and TOKEN."
(let ((value (dune-smie-rules kind token)))
(message
"%s '%s'; sibling-p:%s parent:%s hanging:%s = %s"
kind token
(ignore-errors (smie-rule-sibling-p))
(ignore-errors smie--parent)
(ignore-errors (smie-rule-hanging-p))
value)
value))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Skeletons
;; See Info node "Autotype".
(define-skeleton dune-insert-library-form
"Insert a library stanza."
nil
"(library" > \n
"(name " _ ")" > \n
"(public_name " _ ")" > \n
"(libraries " _ ")" > \n
"(synopsis \"" _ "\"))" > ?\n)
(define-skeleton dune-insert-executable-form
"Insert an executable stanza."
nil
"(executable" > \n
"(name " _ ")" > \n
"(public_name " _ ")" > \n
"(modules " _ ")" > \n
"(libraries " _ "))" > ?\n)
(define-skeleton dune-insert-executables-form
"Insert an executables stanza."
nil
"(executables" > \n
"(names " _ ")" > \n
"(public_names " _ ")" > \n
"(libraries " _ "))" > ?\n)
(define-skeleton dune-insert-rule-form
"Insert a rule stanza."
nil
"(rule" > \n
"(targets " _ ")" > \n
"(deps " _ ")" > \n
"(action (" _ ")))" > ?\n)
(define-skeleton dune-insert-ocamllex-form
"Insert an ocamllex stanza."
nil
"(ocamllex (" _ "))" > ?\n)
(define-skeleton dune-insert-ocamlyacc-form
"Insert an ocamlyacc stanza."
nil
"(ocamlyacc (" _ "))" > ?\n)
(define-skeleton dune-insert-menhir-form
"Insert a menhir stanza."
nil
"(menhir" > \n
"((modules (" _ "))))" > ?\n)
(define-skeleton dune-insert-alias-form
"Insert an alias stanza."
nil
"(alias" > \n
"(name " _ ")" > \n
"(deps " _ "))" > ?\n)
(define-skeleton dune-insert-install-form
"Insert an install stanza."
nil
"(install" > \n
"(section " _ ")" > \n
"(files " _ "))" > ?\n)
(define-skeleton dune-insert-copyfiles-form
"Insert a copy_files stanza."
nil
"(copy_files " _ ")" > ?\n)
(define-skeleton dune-insert-test-form
"Insert a test stanza."
nil
"(test" > \n
"(name " _ "))" > ?\n)
(define-skeleton dune-insert-tests-form
"Insert a tests stanza."
nil
"(tests" > \n
"(names " _ "))" > ?\n)
(define-skeleton dune-insert-env-form
"Insert a env stanza."
nil
"(env" > \n
"(" _ " " _ "))" > ?\n)
(define-skeleton dune-insert-ignored-subdirs-form
"Insert a ignored_subdirs stanza."
nil
"(ignored_subdirs (" _ "))" > ?\n)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar dune-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "\C-c\C-c" 'compile)
(define-key map "\C-c.l" 'dune-insert-library-form)
(define-key map "\C-c.e" 'dune-insert-executable-form)
(define-key map "\C-c.x" 'dune-insert-executables-form)
(define-key map "\C-c.r" 'dune-insert-rule-form)
(define-key map "\C-c.p" 'dune-insert-ocamllex-form)
(define-key map "\C-c.y" 'dune-insert-ocamlyacc-form)
(define-key map "\C-c.m" 'dune-insert-menhir-form)
(define-key map "\C-c.a" 'dune-insert-alias-form)
(define-key map "\C-c.i" 'dune-insert-install-form)
(define-key map "\C-c.c" 'dune-insert-copyfiles-form)
(define-key map "\C-c.t" 'dune-insert-tests-form)
(define-key map "\C-c.v" 'dune-insert-env-form)
(define-key map "\C-c.d" 'dune-insert-ignored-subdirs-form)
map)
"Keymap used in dune mode.")
(defun dune-build-menu ()
"Build the menu for `dune-mode'."
(easy-menu-define
dune-mode-menu (list dune-mode-map)
"dune mode menu."
'("Dune/jbuild"
("Stanzas"
["library" dune-insert-library-form t]
["executable" dune-insert-executable-form t]
["executables" dune-insert-executables-form t]
["rule" dune-insert-rule-form t]
["alias" dune-insert-alias-form t]
["ocamllex" dune-insert-ocamllex-form t]
["ocamlyacc" dune-insert-ocamlyacc-form t]
["menhir" dune-insert-menhir-form t]
["install" dune-insert-install-form t]
["copy_files" dune-insert-copyfiles-form t]
["test" dune-insert-test-form t]
["env" dune-insert-env-form t]
["ignored_subdirs" dune-insert-ignored-subdirs-form t]
)))
(easy-menu-add dune-mode-menu))
;;;###autoload
(define-derived-mode dune-mode prog-mode "dune"
"Major mode to edit dune files.
For customization purposes, use `dune-mode-hook'."
(set (make-local-variable 'font-lock-defaults) '(dune-font-lock-keywords))
(set (make-local-variable 'comment-start) ";")
(set (make-local-variable 'comment-end) "")
(setq indent-tabs-mode nil)
(set (make-local-variable 'require-final-newline) mode-require-final-newline)
(smie-setup dune-smie-grammar #'dune-smie-rules)
(dune-build-menu))
;;;###autoload
(add-to-list 'auto-mode-alist
'("\\(?:\\`\\|/\\)dune\\(?:\\.inc\\|\\-project\\|\\-workspace\\)?\\'" . dune-mode))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Interacting with dune
(defcustom dune-command "dune"
"The dune command."
:type 'string)
;;;###autoload
(defun dune-promote ()
"Promote the correction for the current file."
(interactive)
(if (buffer-modified-p)
(error "Cannot promote as buffer is modified")
(shell-command
(format
"%s promote %s" dune-command
(shell-quote-argument (file-name-nondirectory (buffer-file-name)))))
(revert-buffer nil t)))
;;;###autoload
(defun dune-runtest-and-promote ()
"Run tests in the current directory and promote the current buffer."
(interactive)
(compile (format "%s build @@runtest" dune-command))
(dune-promote))
(defun dune-project-p (directory)
"Return t if DIRECTORY is a dune project."
(file-exists-p (expand-file-name "dune-project" directory)))
(defun dune-workspace-p (directory)
"Return t if DIRECTORY is a dune workspace."
(file-exists-p (expand-file-name "dune-workspace" directory)))
(defun dune-root (&optional directory)
"Return the root directory of the dune project of DIRECTORY.
DIRECTORY defaults to `default-directory' if not provided."
(let*
(root
workspace
(dir (or directory default-directory))
(project-p (lambda (dir)
(cond
((dune-workspace-p dir)
(setq workspace t)
t)
((and
(not workspace)
(dune-project-p dir))
t)))))
(while dir
(setq dir (locate-dominating-file dir project-p))
(when dir
(setq root dir
dir (file-name-parent-directory dir))))
root))
(provide 'dune)
;;; dune.el ends here