Emacs as a F# IDE (part1)
source link: https://zwild.github.io/posts/emacs-as-a-fsharp-ide-part1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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.
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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK