The autocompletion is usually unobstrusive, but it insists on trying to complete the “in” in “let/in” to other things!
i haven’t experienced this for let in
but i have for sig end
. haven’t figured out what causes it yet.
Similarly, I have yet to figure out how the indentation engine guesses things, and fight it a lot. One issue is it seems to want 2 space indents, but the editors line shifting (“>” and “<”) locks to 4 space tab locations, and I can’t find where these settings are documented?
ive found that its usually easier to search for the variables a mode defines rather than search docs for this kind of thing. doing M-x helpful-variable
and typing in tuareg indent
returned a few results for me. you can probably use setq
to change those values however you like.
that said, i tend to let ocpindent / ocamlformat do those things for me automatically via the format
doom module. those tools have their own configuration files per project.
In evil mode (the vim-like editing), I can’t send stuff to the repl. I need to switch to emacs bindings, which my fingers hate, to use the repl in emacs.
fairly sure in vim mode g r
is bound to +eval:region
by default, which will automatically try to open a repl for the given mode & send the selected region to it. ive done that a bunch of times. g R
will send the whole buffer.
- Get type information from throughout the project
- Find and fix typing mistakes
this should just work if you have the ocaml lsp in your environment. M-x env
will let you search the env for OPAM stuff. the doom binary has a doom env
command that lets you save your current env to a file that doom will load.
Write and use expect tests
when i do SPC p T
it automatically defaults to dune runtest
in my project. my config (below) has some custom functions that let you run all the inline tests in your current buffer via SPC m i f
.
good luck!
;; ## OCAML
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(use-package! tuareg
:config
(map! :localleader :map tuareg-mode-map
:desc "find [a]lternate file" "a" #'tuareg-find-alternate-file
:desc "compile" "c" #'my/dune-build
:desc "run tests" "t" #'my/dune-runtest
:desc "promote tests" "p" #'my/dune-promote
(:prefix ("i". "inline tests")
:desc "Run tests in [f]ile" "f" #'my/dune-run-inline-tests
:desc "Promote tests in file" "F" #'my/dune-promote-inline-tests
:desc "Promote tests in file" "a" #'my/dune-run-all-inline-tests
:desc "See help" "h" #'my/dune-inline-test-help
))
(add-hook! 'tuareg-mode-hook #'my/disable-evil-continue-comments-h)
(add-hook! 'tuareg-mode-hook #'my/tuareg-comment-style)
(define-derived-mode tuareg-mli-mode tuareg-mode "Tuareg Mli")
(set-tree-sitter-lang! 'tuareg-mli-mode 'ocaml-interface)
(add-hook! 'tuareg-mli-mode-hook #'tree-sitter!)
(setq auto-mode-alist (delete '("\\.ml[ip]?\\'" . tuareg-mode) auto-mode-alist))
(add-to-list 'auto-mode-alist '("\\.ml[p]?\\'" . tuareg-mode))
(add-to-list 'auto-mode-alist '("\\.mli\\'" . tuareg-mli-mode))
)
(use-package! utop
:config
(setq! utop-command "dune utop --no-print-directory --display quiet . -- -emacs"
utop-edit-command nil)
(map! :map utop-mode-map
:nvm "i" #'my/utop-insert-at-prompt
:nvmi "C-p" #'utop-history-goto-prev
:nvmi "C-n" #'utop-history-goto-next
:i "C-k" #'utop-history-goto-prev
:i "C-j" #'utop-history-goto-next))
(after! (:and apheleia ocp-indent)
(add-to-list 'apheleia-formatters '(ocp-indent . ("ocp-indent"))))
(defun my/utop-insert-at-prompt ()
(interactive)
(if (< (point) utop-prompt-max)
(progn (goto-char (point-max))))
(evil-insert 1))
(defun my/disable-evil-continue-comments-h ()
(setq-local +evil-want-o/O-to-continue-comments nil)
(setq-local +default-want-RET-continue-comments nil))
(defun my/tuareg-comment-style ()
(print "wtf bro")
(setq-local comment-style 'multi-line)
(setq-local comment-continue " "))
(defun my/projectile-compile (cmd)
(projectile-with-default-dir (projectile-acquire-root)
(compile cmd)))
;; TODO: make this handle the case where theres more than one .exe
(defun my/dune-inline-test-runner ()
(let ((path (s-trim (projectile-with-default-dir (projectile-acquire-root)
(shell-command-to-string
(s-concat "find _build -path '*sandbox' -prune "
"-o -name 'inline_test_runner*.exe' "
"-print" ))))))
(--if-let (s-match (s-concat "_build/.?*/\\(.?*\\)/" ; get dir
"\\.\\(.?*\\).inline-tests/" ; get target
"inline_test_runner_.?*.exe") path)
(list 'path (car it) 'dir (cadr it) 'target (caddr it))
nil)))
(defcustom my/dune-inline-test-args "-verbose -no-color -diff-cmd 'diff -u'"
"arguments that are always passed to the inline test runner"
:group 'my/dune :type 'string)
;; TODO: make this choose the corresponding test runner based on
;; the directory of the current file (my/projectile-buffer-path)
(defun my/dune-inline-test-cmd (args)
(let* ((runner (my/dune-inline-test-runner))
(cmd (format "%s inline-test-runner %s %s %s"
(plist-get runner 'path)
(plist-get runner 'target)
my/dune-inline-test-args
args)))
(format "dune exec -- %s" cmd)))
(defun my/dune-inline-test-help ()
"runs -help command on inline test runner"
(interactive)
(my/projectile-compile (my/dune-inline-test-cmd "-help")))
(defun my/dune-run-inline-tests ()
"runs inline tests in the current file"
(interactive)
(let* ((file (f-filename (buffer-file-name)))
(cmd (format "-only-test %s" file)))
(my/projectile-compile (my/dune-inline-test-cmd cmd))))
(defun my/dune-promote-inline-tests ()
"promotes inline tests in the current file"
(interactive)
(let* ((file (f-filename (buffer-file-name)))
(cmd (format "-in-place -only-test %s" file)))
(my/projectile-compile (my/dune-inline-test-cmd cmd))))
(defun my/dune-run-all-inline-tests ()
"runs all inline tests in the project"
(interactive)
(my/projectile-compile (my/dune-inline-test-cmd "")))
(defun my/dune-build ()
"calls dune build" (interactive) (my/projectile-compile "dune build"))
(defun my/dune-runtest ()
"calls dune runtest" (interactive) (my/projectile-compile "dune runtest"))
(defun my/dune-promote ()
"calls dune promote" (interactive) (my/projectile-compile "dune promote"))