6

Emacs as a F# IDE (part1)

 2 years ago
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.
neoserver,ios ssh client

Emacs as a F# IDE (part1)

# Sep 3, 2018


tags: fsharp, emacs

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.

/img/completion.gif

/img/flycheck.gif

/img/eldoc.png

Jump to definition

/img/jump.gif

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.")))))

/img/indent-region.gif

(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)))

/img/indent-defun.gif

(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)))

/img/load-file.gif

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)))))

/img/add-to-proj.gif

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)))))

/img/fsharp-compile-project.gif

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)))

/img/prettify.png

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.

/img/eshell-dotnet-demo.gif

Conclusion

You can get all the code here.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK