Why Emacs
Emacs need years to get familiar with. But I think it's worth it. When you are familiar with it, you can customize it as you want, you can write whatever you want in your mind in a few of elisp codes. And there are also tons of libaries available.
F# is a great language. The fsharp-mode is full featured, and here is an introduction and some enhancement.
How
Install
We use fsharp-mode for code highlight, completion, flycheck and mainly everything. And we use ob-fsharp for code evaluating in org-mode.
(require-package 'fsharp-mode 'ob-fsharp)
The libraries are avalible at melpa. Here is the definition of require-package
.
(defun require-package (&rest packages)
(dolist (p packages)
(unless (package-installed-p p)
(condition-case nil (package-install p)
(error
(package-refresh-contents)
(package-install p))))))
Completion, Code Check, Type Signature
Code completion, code checking and displaying type signature will work when you setup your project correctly and download all the F# dependencies you need for the project.
Jump to definition
They are bound to M-.
and M-,
.
Indentation
Fontomas is a F# source code formatter. But there isn't a elisp library for it. However we can write it.
(defun fsharp-fantomas-format-region (start end)
(interactive "r")
(let ((source (shell-quote-argument (buffer-substring-no-properties start end)))
(ok-buffer "*fantomas*")
(error-buffer "*fantomas-errors*"))
(save-window-excursion
(shell-command-on-region
start end (format "fantomas --indent 2 --pageWidth 99 --stdin %s --stdout" source)
ok-buffer nil error-buffer)
(if (get-buffer error-buffer)
(progn
(kill-buffer error-buffer)
(message "Can't format region."))
(delete-region start end)
(insert (with-current-buffer ok-buffer
(s-chomp (buffer-string))))
(delete-trailing-whitespace)
(message "Region formatted.")))))
(defun fsharp-fantomas-format-defun ()
(interactive)
(let ((origin (point))
(start) (end))
(fsharp-beginning-of-block)
(setq start (point))
(fsharp-end-of-block)
;; skip whitespace, empty lines, comments
(while (and (not (= (line-number-at-pos) 1))
(s-matches? "^\n$\\|^//\\|^(\\*" (thing-at-point 'line)))
(forward-line -1))
(move-end-of-line 1)
(setq end (point))
(fsharp-fantomas-format-region start end)
(goto-char origin)))
(defun fsharp-fantomas-format-buffer ()
(interactive)
(let ((origin (point)))
(fsharp-fantomas-format-region (point-min) (point-max))
(goto-char origin)))
Load file to inferior buffer
fsharp-mode provide fsharp-load-buffer-file
, which load the current buffer to the inferior fsharp process.
However when you need to load some other files as dependencies, you don't have a function like fsharp-load-file
. Here is one:
(defun fsharp-load-file (file-name)
(interactive (comint-get-source "Load F# file: " nil '(fsharp-mode) t))
(let ((command (concat "#load \"" file-name "\"")))
(comint-check-source file-name)
(fsharp-simple-send inferior-fsharp-buffer-name command)))
Add file to fsproj
When you edit a new F# file, you need to write it to the fsproj file. And when you delete a F# file, you need to remove it from the fsproj file, too.
(defun fsharp-add-this-file-to-proj ()
(interactive)
(when-let* ((file-long (f-this-file))
(project (fsharp-mode/find-fsproj file-long))
(file (f-filename file-long)))
(with-current-buffer (find-file-noselect project)
(goto-char (point-min))
(unless (re-search-forward file nil t)
(when (and (re-search-forward "<Compile Include=" nil t)
(re-search-backward "<" nil t))
(insert (format "<Compile Include=\"%s\" />\n " file))
(save-buffer))))))
(defun fsharp-remove-this-file-from-proj ()
(interactive)
(when-let* ((file-long (f-this-file))
(project (fsharp-mode/find-fsproj file-long))
(file (f-filename file-long)))
(with-current-buffer (find-file-noselect project)
(goto-char (point-min))
(when (re-search-forward (format "<Compile Include=\"%s\" />" file) nil t)
(move-beginning-of-line 1)
(kill-line)
(kill-line)
(save-buffer)))))
Compile the project
This definition will change the compile directory based on the project type(fake, dotnet), and call compile
interactively.
(defun fsharp-compile-project ()
"Compile project using fake or dotnet."
(interactive)
(let ((fake-dir (locate-dominating-file default-directory "build.fsx"))
(proj (fsharp-mode/find-fsproj (or (f-this-file) ""))))
(cond (fake-dir (let ((default-directory fake-dir)
(compile-command "fake build"))
(call-interactively 'compile)))
(proj (let ((compile-command (format "dotnet build \"%s\"" proj)))
(call-interactively 'compile)))
(t (call-interactively 'compile)))))
Prettify symbols
You can change the symbols using prettify-symbols-mode
.
(defun fsharp-enable-prettify-symbols ()
(let ((alist '(("->" . ?→)
("<-" . ?←)
("|>" . ?⊳)
("<|" . ?⊲))))
(setq-local prettify-symbols-alist alist)))
(add-hook 'fsharp-mode-hook
(lambda ()
(fsharp-enable-prettify-symbols)))
Manage project
I use eshell
to init, install, test, run and compile a F# project. I will introduce how to use that and how to write completion for eshell in the next post.
Conclusion
You can get all the code here.