~theo/fsharp-mode

6804bef9f86b88d9e1259c24211fc76daa6889df — Theodor Thornhill 2 years ago 781e75c
Move files around
3 files changed, 339 insertions(+), 571 deletions(-)

D README.org
D fsharp-mode-font.el
M fsharp-mode.el
D README.org => README.org +0 -200
@@ 1,200 0,0 @@
[[http://melpa.org/#/fsharp-mode][file:http://melpa.org/packages/fsharp-mode-badge.svg]]
[[https://stable.melpa.org/#/fsharp-mode][file:https://stable.melpa.org/packages/fsharp-mode-badge.svg]]
[[https://github.com/fsharp/emacs-fsharp-mode/actions][file:https://github.com/fsharp/emacs-fsharp-mode/workflows/CI/badge.svg]]
* fsharp-mode

Provides support for the F# language in Emacs. Includes the following features:

- Syntax highlighting and indentation
- Support for F# Interactive
- Via [[https://github.com/joaotavora/eglot/issues][Eglot]] LSP-client integration:
  - Displays type signatures and tooltips
  - Flymake
  - Completion
  - Jump to definition [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Xref.html][Find Identifier References]] (Xref)

** LSP mode
Previous versions of =fsharp-mode= (mis)used a second repository [[https://github.com/rneatherway/emacs-fsharp-mode-bin][emacs-fsharp-mode-bin]] to distribute =fsautocomplete.exe= and =fsharp-mode= together.

The current version of =fsharp-mode= installs =fsautocomplete.exe= automatically via [[https://github.com/joaotavora/eglot][eglot-fsharp]] (part of this repo) or
[[https://github.com/emacs-lsp/lsp-mode][lsp-mode]] (untested).

=fsharp-mode= is tested with Emacs 26.1+ and NET Core 2.1 (LTS)

** Installation

*** Package

=fsharp-mode= is available on [[https://melpa.org][MELPA]] and can
be installed using the built-in package manager.

If you're not already using MELPA, add the following to your init.el:

#+BEGIN_SRC elisp
  ;;; Initialize MELPA
  (require 'package)
  (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/"))
  (unless package-archive-contents (package-refresh-contents))
  (package-initialize)

  ;;; Install fsharp-mode
  (unless (package-installed-p 'fsharp-mode)
    (package-install 'fsharp-mode))

  (require 'fsharp-mode)
#+END_SRC

If you are a user of [[https://github.com/jwiegley/use-package][use-package]] you can instead do

#+BEGIN_SRC elisp
(use-package fsharp-mode
  :defer t
  :ensure t)
#+END_SRC

*** From source

I recommend to use [[https://cask.github.io/why-cask.html][Cask]]. Add this to your =Cask= file:

#+BEGIN_SRC elisp
(depends-on "fsharp-mode" :git "https://github.com/fsharp/emacs-fsharp-mode.git")
#+END_SRC

** Eglot integration

=eglot-fsharp= is part of this package and provides integration to [[https://github.com/joaotavora/eglot][Eglot]] Emacs LSP client:

   - Automatic =fsautocomplete= download
   - Workaround for non-LSP standard-compliant

Add to your config:
#+BEGIN_SRC elisp
(require 'eglot-fsharp)
#+END_SRC

and execute =M-x eglot=


** Projects

=fsharp-mode= has support for Emacs build-in project management via =project.el=

** Configuration

*** Compiler and REPL paths

The F# compiler and interpreter should be set to good defaults for
your OS as long as the relevant executables can be found on your PATH
or in other standard locations. If you have a non-standard setup you
may need to configure these paths manually.

On Windows:

#+BEGIN_SRC elisp
(setq inferior-fsharp-program "c:\\Path\\To\\Fsi.exe")
#+END_SRC

On Unix-like systems, you must use the *--readline-* flag to ensure F#
Interactive will work correctly with Emacs. Typically =fsi= and =fsc= are
invoked through the shell scripts =fsharpi= and =fsharpc=:

#+BEGIN_SRC elisp
(setq inferior-fsharp-program "path/to/fsharpi --readline-")
#+END_SRC

***  Key Bindings

If you are new to Emacs, you might want to use the menu (call
=menu-bar-mode= if you don't see it). However, it's usually faster to learn
a few useful bindings:

| Key binding      | Description                               |
|------------------+-------------------------------------------|
| =C-c C-r=        | Evaluate region                           |
| =C-c C-f=        | Load current buffer into toplevel         |
| =C-c C-e=        | Evaluate current toplevel phrase          |
| =C-M-x=          | Evaluate current toplevel phrase          |
| =C-M-h=          | Mark current toplevel phrase              |
| =C-c C-s=        | Show interactive buffer                   |
| =C-c C-c=        | Compile with fsc                          |
| =C-c x=          | Run the executable                        |
| =C-c C-a=        | Open alternate file (.fsi or .fs)         |
| =C-c l=          | Shift region to left                      |
| =C-c r=          | Shift region to right                     |
| =C-c <up>=       | Move cursor to the beginning of the block |
| =C-c C-d=, =M-.= | Jump to definition of symbol at point     |
| =C-c C-b=, =M-,= | Return to where point was before jump.    |


To interrupt the interactive mode, use =C-c C-c=. This is useful if your
code does an infinite loop or a very long computation.

If you want to shift the region by 2 spaces, use: =M-2 C-c r=

In the interactive buffer, use ==M-RET= to send the code without
explicitly adding the =;;= thing.


** Editor

In order to change tab size it is possible to put this in emacs profile:

#+BEGIN_SRC elisp
(setq-default fsharp-indent-offset 2)
#+END_SRC

Because the F# language is sensitive to indentation, you might wan't to highlight indentation:

#+BEGIN_SRC elisp
(add-hook 'fsharp-mode-hook 'highlight-indentation-mode)
#+END_SRC

** Troubleshooting

=fsharp-mode= is still under development, so you may encounter some
issues. Please report them so we can improve things! Open an issue on [[https://github.com/fsharp/emacs-fsharp-mode/][Github]].

*** No autocompletion in FSX files

The root cause is documented in this Ionide issue:  [[https://github.com/ionide/ionide-vscode-fsharp/issues/1244][4.2.0 - No auto complete or typechecking in FSX files]]

As a workaround can add a reference to the facade netstandard assembly (path is platform/SDK-dependent).

On Arch Linux using [[https://aur.archlinux.org/packages/dotnet-sdk-lts-bin][dotnet sdk lts]] add this to your =fsx= file:
#+BEGIN_SRC fsharp
#r "/opt/dotnet/sdk/2.1.801/ref/netstandard.dll"
#+END_SRC

*** Project file issues

If your project file does not seem to be being parsed correctly, so
that you have missing references or other incorrect intellisense
results, it is possible to obtain a detailed log of LSP events in this buffers:


- =*EGLOT (PROJECT/fsharp-mode) stderr*=
- =*EGLOT (PROJECT/fsharp-mode) output*=
- =*EGLOT (PROJECT/fsharp-mode) events*=

** Contributing

This project is maintained by the
[[http://fsharp.org/][F# Software Foundation]], with the repository hosted
on [[https://github.com/fsharp/emacs-fsharp-mode][GitHub]].

Pull requests are welcome. Please run the test-suite with =make
test= before submitting a pull request.

*** Maintainers

The maintainers of this repository appointed by the F# Core Engineering Group are:

 - [[https://github.com/juergenhoetzel][Jürgen Hötzel]], [[http://github.com/forki][Steffen Forkmann]], [[http://github.com/kjnilsson][Karl Nilsson]] and [[http://github.com/guillermooo][Guillermo López-Anglada]]
 - The primary maintainer for this repository is [[https://github.com/juergenhoetzel][Jürgen Hötzel]]

Previous maintainers:
 - [[https://github.com/rneatherway][Robin Neatherway]]





D fsharp-mode-font.el => fsharp-mode-font.el +0 -371
@@ 1,371 0,0 @@
;;; fsharp-mode-font.el --- Syntax highlighting for F#

;; Copyright (C) 1997 INRIA

;; Author: 1993-1997 Xavier Leroy, Jacques Garrigue and Ian T Zimmerman
;;         2010-2011 Laurent Le Brun <laurent@le-brun.eu>
;; Maintainer: Robin Neatherway <robin.neatherway@gmail.com>
;; Keywords: languages

;; This file is not part of GNU Emacs.

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;;; Code:

;; (require 'fsharp-mode)
;; (require 'dash)

(defgroup fsharp-ui nil
  "F# UI group for the defcustom interface."
  :prefix "fsharp-ui-"
  :group 'fsharp
  :package-version '(fsharp-mode . "1.9.2"))

(defface fsharp-ui-generic-face
  '((t (:inherit default)))
  "Preprocessor face"
  :group 'fsharp-ui)

(defface fsharp-ui-operator-face
  '((t (:foreground "LightSkyBlue")))
  "Preprocessor face"
  :group 'fsharp-ui)

(defface fsharp-ui-warning-face
  '((t (:inherit font-lock-warning-face)))
  "Face for warnings."
  :group 'fsharp-ui)

(defface fsharp-ui-error-face
  '((t (:inherit font-lock-error-face :underline t)))
  "Face for errors"
  :group 'fsharp-ui)

(defmacro def-fsharp-compiled-var (sym init &optional docstring)
  "Defines a SYMBOL as a constant inside an eval-and-compile form
with initial value INITVALUE and optional DOCSTRING."
  `(eval-and-compile
     (defvar ,sym ,init ,docstring)))

(def-fsharp-compiled-var fsharp-shebang-regexp
  "\\(^#!.*?\\)\\([A-Za-z0-9_-]+\\)$"
  "Capture the #! and path of a shebag in one group and the
  executable in another.")

(def-fsharp-compiled-var fsharp-access-control-regexp
  "private\\s-+\\|internal\\s-+\\|public\\s-+"
  "Match `private', `internal', or `public', followed by a space,
  with no capture.")

(def-fsharp-compiled-var fsharp-access-control-regexp-noncapturing
  (format "\\(?:%s\\)" fsharp-access-control-regexp)
  "Same as `fsharp-access-control-regexp', but captures")

(def-fsharp-compiled-var fsharp-inline-rec-regexp
  "inline\\s-+\\|rec\\s-+"
  "Match `inline' or `rec', followed by a space.")

(def-fsharp-compiled-var fsharp-inline-rec-regexp-noncapturing
  (format "\\(?:%s\\)" fsharp-inline-rec-regexp)
  "Match `inline' or `rec', followed by a space, with no capture.")

(def-fsharp-compiled-var fsharp-valid-identifier-regexp
  "[A-Za-z0-9_']+"
  "Match a normal, valid F# identifier -- alphanumeric characters
  plus ' and underbar. Does not capture")

(def-fsharp-compiled-var fsharp-function-def-regexp
  (concat "\\<\\(?:let\\|and\\|with\\)\\s-+"
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          (format "\\(%s\\)" fsharp-valid-identifier-regexp)
          "\\(?:\\s-+[A-Za-z_]\\|\\s-*(\\)" ;; matches function arguments or open-paren; unclear why 0-9 not in class
          ))

(def-fsharp-compiled-var fsharp-pattern-function-regexp
  (concat "\\<\\(?:let\\|and\\)\\s-+"
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          (format "\\(%s\\)" fsharp-valid-identifier-regexp)
          "\\s-*=\\s-*function")
  "Matches an implicit matcher, eg let foo m = function | \"cat\" -> etc.")

;; Note that this regexp is used for iMenu. To font-lock active patterns, we
;; need to use an anchored match in fsharp-font-lock-keywords.
(def-fsharp-compiled-var fsharp-active-pattern-regexp
  (concat "\\<\\(?:let\\|and\\)\\s-+"
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          "(\\(|[A-Za-z0-9_'|]+|\\))\\(?:\\s-+[A-Za-z_]\\|\\s-*(\\)"))

(def-fsharp-compiled-var fsharp-member-access-regexp
  "\\<\\(?:override\\|member\\|abstract\\)\\s-+"
  "Matches members declarations and modifiers on classes.")

(def-fsharp-compiled-var fsharp-member-function-regexp
  (concat fsharp-member-access-regexp
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          "\\(?:" fsharp-valid-identifier-regexp "\\.\\)?"
          "\\(" fsharp-valid-identifier-regexp "\\)")
  "Captures the final identifier in a member function declaration.")

(def-fsharp-compiled-var fsharp-overload-operator-regexp
  (concat fsharp-member-access-regexp
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          "\\(([!%&*+-./<=>?@^|~]+)\\)")
  "Match operators when overloaded by a type/class.")

(def-fsharp-compiled-var fsharp-constructor-regexp
  (concat "^\\s-*"
          fsharp-access-control-regexp-noncapturing "*"
          "\\<\\(new\\) *(.*)[^=]*=")
  "Matches the `new' keyword in a constructor")

(def-fsharp-compiled-var fsharp-type-def-regexp
  (concat "^\\s-*\\<\\(?:type\\|inherit\\)\\s-+"
          fsharp-access-control-regexp-noncapturing "*" ;; match access control 0 or more times
          "\\([A-Za-z0-9_'.]+\\)"))

(def-fsharp-compiled-var fsharp-var-or-arg-regexp
  "\\_<\\([A-Za-z_][A-Za-z0-9_']*\\)\\_>")

(def-fsharp-compiled-var fsharp-explicit-field-regexp
  (concat "^\\s-*\\(?:val\\|abstract\\)\\s-*\\(?:mutable\\s-+\\)?"
          fsharp-access-control-regexp-noncapturing "*" ;; match access control 0 or more times
          "\\([A-Za-z_][A-Za-z0-9_']*\\)\\s-*:\\s-*\\([A-Za-z_][A-Za-z0-9_'<> \t]*\\)"))

(def-fsharp-compiled-var fsharp-attributes-regexp
  "\\(\\[<[A-Za-z0-9_]+[( ]?\\)\\(\".*\"\\)?\\()?>\\]\\)"
  "Match attributes like [<EntryPoint>]; separately groups contained strings in attributes like [<Attribute(\"property\")>]")

;; F# makes extensive use of operators, many of which have some kind of
;; structural significance.
;;
;; In particular:
;; (| ... |)                 -- banana clips for Active Patterns (handled separately)
;; <@ ... @> and <@@ ... @@> -- quoted expressions
;; <| and |>                 -- left and right pipe (also <||, <|||, ||>, |||>)
;; << and >>                 -- function composition
;; |                         -- match / type expressions

(def-fsharp-compiled-var fsharp-operator-quote-regexp
  "\\(<@\\{1,2\\}\\)\\(?:.*\\)\\(@\\{1,2\\}>\\)"
  "Font lock <@/<@@ and @>/@@> operators.")

(def-fsharp-compiled-var fsharp-operator-pipe-regexp
  "<|\\{1,3\\}\\||\\{1,3\\}>"
  "Match the full range of pipe operators -- |>, ||>, |||>, etc.")

(def-fsharp-compiled-var fsharp-operator-case-regexp
  "\\s-+\\(|\\)[A-Za-z0-9_' ]"
  "Match literal | in contexts like match and type declarations.")

(defun fsharp-var-pre-form ()
  (save-excursion
    (re-search-forward "\\(:\\s-*\\w[^)]*\\)?=" nil t)
    (match-beginning 0)))

(defun fsharp-fun-pre-form ()
  (save-excursion
    (search-forward "->")))

;; Preprocessor directives (3.3)
(def-fsharp-compiled-var fsharp-ui-preproessor-directives
  '("#if" "#else" "#endif" "#light"))

;; Compiler directives (12.4)
(def-fsharp-compiled-var fsharp-ui-compiler-directives
  '("#nowarn" "#load" "#r" "#reference" "#I"
    "#Include" "#q" "#quit" "#time" "#help"))

;; Lexical matters (18.4)
(def-fsharp-compiled-var fsharp-ui-lexical-matters
  '("#indent"))

;; Line Directives (3.9)
(def-fsharp-compiled-var fsharp-ui-line-directives
  '("#line"))

;; Identifier replacements (3.11)
(def-fsharp-compiled-var fsharp-ui-identifier-replacements
  '("__SOURCE_DIRECTORY__" "__SOURCE_FILE__" "__LINE__"))

;; F# keywords (3.4)
(def-fsharp-compiled-var fsharp-ui-fsharp-threefour-keywords
  '("abstract" "and" "as" "assert" "base" "begin"
    "class" "default" "delegate" "do" "do!" "done"
    "downcast" "downto" "elif" "else" "end"
    "exception" "extern" "false" "finally" "for" "fun"
    "function" "global" "if" "in" "inherit" "inline"
    "interface" "internal" "lazy" "let" "let!"
    "match" "member" "module" "mutable" "namespace"
    "new" "not" "null" "of" "open" "or" "override"
    "private" "public" "rec" "return" "return!"
    "select" "static" "struct" "then" "to" "true"
    "try" "type" "upcast" "use" "use!"  "val" "void"
    "when" "while" "with" "yield" "yield!"))

;; "Reserved because they are reserved in OCaml"
(def-fsharp-compiled-var fsharp-ui-ocaml-reserved-words
  '("asr" "land" "lor" "lsl" "lsr" "lxor" "mod" "sig"))

;; F# reserved words for future use
(def-fsharp-compiled-var fsharp-ui-reserved-words
  '("atomic" "break" "checked" "component" "const"
    "constraint" "constructor" "continue" "eager"
    "event" "external" "fixed" "functor" "include"
    "method" "mixin" "object" "parallel" "process"
    "protected" "pure" "sealed" "tailcall" "trait"
    "virtual" "volatile"))

;; RMD 2016-09-30 -- This was pulled out separately with the following comment
;; when I got here. Not clear to me why it's on it's own, or even precisely what
;; the comment means. But: `async' is a valid F# keyword and needs to go someplace,
;; so I've left it here. For now.
;;
;; Workflows not yet handled by fsautocomplete but async
;; always present
(def-fsharp-compiled-var fsharp-ui-async-words
  '("async")
  "Just the word async, in a list.")

(def-fsharp-compiled-var fsharp-ui-word-list-regexp
  (regexp-opt
   `(,@fsharp-ui-async-words
     ,@fsharp-ui-compiler-directives
     ,@fsharp-ui-fsharp-threefour-keywords
     ,@fsharp-ui-identifier-replacements
     ,@fsharp-ui-lexical-matters
     ,@fsharp-ui-ocaml-reserved-words
     ,@fsharp-ui-preproessor-directives
     ,@fsharp-ui-reserved-words
     ,@fsharp-ui-line-directives)
   'symbols))

(defconst fsharp-font-lock-keywords
  (eval-when-compile
    `((,fsharp-ui-word-list-regexp 0 font-lock-keyword-face)
      ;; shebang
      (,fsharp-shebang-regexp
       (1 font-lock-comment-face)
       (2 font-lock-keyword-face))
      ;; attributes
      (,fsharp-attributes-regexp
       (1 font-lock-preprocessor-face)
       (2 font-lock-string-face nil t)
       (3 font-lock-preprocessor-face))
      ;; ;; type defines
      (,fsharp-type-def-regexp 1 font-lock-type-face)
      (,fsharp-function-def-regexp 1 font-lock-function-name-face)
      (,fsharp-pattern-function-regexp 1 font-lock-function-name-face)
      ;; Active Pattern
      ("(|" (0 'fsharp-ui-operator-face)
       ("\\([A-Za-z'_]+\\)\\(|)?\\)"
        nil nil
        (1 font-lock-function-name-face)
        (2 'fsharp-ui-operator-face)))
      (,fsharp-operator-pipe-regexp . 'fsharp-ui-operator-face)
      (,fsharp-member-function-regexp 1 font-lock-function-name-face)
      (,fsharp-overload-operator-regexp 1 font-lock-function-name-face)
      (,fsharp-constructor-regexp 1 font-lock-function-name-face)
      (,fsharp-operator-case-regexp 1 'fsharp-ui-operator-face)
      (,fsharp-operator-quote-regexp  (1 'fsharp-ui-operator-face)
                                      (2 'fsharp-ui-operator-face))
      ("[^:]:\\s-*\\(\\<[A-Za-z0-9_' ]*[^ ;\n,)}=<-]\\)\\(<[^>]*>\\)?"
       (1 font-lock-type-face)
       ;; 'prevent generic type arguments from being rendered in variable face
       (2 'fsharp-ui-generic-face nil t))
      (,(format "^\\s-*\\<\\(let\\|use\\|override\\|member\\|and\\|\\(?:%snew\\)\\)\\_>"
                (concat fsharp-access-control-regexp "*"))
       (0 font-lock-keyword-face) ; let binding and function arguments
       (,fsharp-var-or-arg-regexp
        (fsharp-var-pre-form) nil
        (1 font-lock-variable-name-face nil t)))
      ("\\<fun\\>"
       (0 font-lock-keyword-face) ; lambda function arguments
       (,fsharp-var-or-arg-regexp
        (fsharp-fun-pre-form) nil
        (1 font-lock-variable-name-face nil t)))
      (,fsharp-type-def-regexp
       (0 'font-lock-keyword-face) ; implicit constructor arguments
       (,fsharp-var-or-arg-regexp
        (fsharp-var-pre-form) nil
        (1 font-lock-variable-name-face nil t)))
      (,fsharp-explicit-field-regexp
       (1 font-lock-variable-name-face)
       (2 font-lock-type-face))

      ;; open namespace
      ("\\<open\s\\([A-Za-z0-9_.]+\\)" 1 font-lock-type-face)

      ;; module/namespace
      ("\\_<\\(?:module\\|namespace\\)\s\\([A-Za-z0-9_.]+\\)" 1 font-lock-type-face)
      )))

(defun fsharp-ui-setup-font-lock ()
  "Set up font locking for F# Mode."
  (setq font-lock-defaults
        '(fsharp-font-lock-keywords)))

(add-hook 'fsharp-mode-hook #'fsharp-ui-setup-font-lock)

(defun fsharp--syntax-propertize-function (start end)
  (goto-char start)
  (fsharp--syntax-string end)
  (funcall (syntax-propertize-rules
            ("\\(@\\)\"" (1 (prog1 "|" (fsharp--syntax-string end)))) ; verbatim string
            ("\\(\"\\)\"\"" (1 (prog1 "|" (fsharp--syntax-string end)))) ; triple-quoted string
            ("\\('\\)\\(?:[^\n\t\r\b\a\f\v\\\\]\\|\\\\[\"'ntrbafv\\\\]\\|\\\\u[0-9A-Fa-f]\\{4\\}\\|\\\\[0-9]\\{3\\}\\)\\('\\)"
             (1 "|") (2 "|")) ; character literal
            ("\\((\\)/" (1 "()"))
            ("\\(\(\\)\\*[!%&*+-\\./<=>@^|~?]*[\n\t\r\b\a\f\v ]*\)" (1 "()")) ; symbolic operator starting (* is not a comment
            ("\\(/\\)\\*" (1 ".")))
           start end))

(defun fsharp--syntax-string (end)
  (let* ((pst (syntax-ppss))
         (instr (nth 3 pst))
         (start (nth 8 pst)))
    (when (eq t instr) ; Then we are in a custom string
      (cond
       ((eq ?@ (char-after start)) ; Then we are in a verbatim string
        (while
            (when (re-search-forward "\"\"?" end 'move)
              (if (> (- (match-end 0) (match-beginning 0)) 1)
                  t ;; Skip this "" and keep looking further.
                (put-text-property (- (match-beginning 0) 1) (- (match-end 0) 1)
                                   'syntax-table (string-to-syntax "."))
                (put-text-property (match-beginning 0) (match-end 0)
                                   'syntax-table (string-to-syntax "|"))
                nil)))
        )

       (t ; Then we are in a triple-quoted string
        (when (re-search-forward "\"\"\"" end 'move)
          (put-text-property (- (match-beginning 0) 1) (match-beginning 0)
                             'syntax-table (string-to-syntax "."))
          (put-text-property (match-beginning 0) (match-end 0)
                             'syntax-table (string-to-syntax "|")))
        )))))

(provide 'fsharp-mode-font)

;;; fsharp-mode-font.el ends here

M fsharp-mode.el => fsharp-mode.el +339 -0
@@ 31,6 31,7 @@
;;; Code:

(require 'compile)
(require 'fsharp-mode-font)

(defgroup fsharp nil
  "Support for the Fsharp programming language, <http://www.fsharp.net/>"


@@ 189,6 190,344 @@ ambiguous, multiple invocations will indent tabstops forward."
   (indent-next-tab-stop
    (save-excursion (back-to-indentation) (current-column)) t)))


;;; Font locking
(defgroup fsharp-ui nil
  "F# UI group for the defcustom interface."
  :prefix "fsharp-ui-"
  :group 'fsharp
  :package-version '(fsharp-mode . "1.9.2"))

(defface fsharp-ui-generic-face
  '((t (:inherit default)))
  "Preprocessor face"
  :group 'fsharp-ui)

(defface fsharp-ui-operator-face
  '((t (:foreground "LightSkyBlue")))
  "Preprocessor face"
  :group 'fsharp-ui)

(defface fsharp-ui-warning-face
  '((t (:inherit font-lock-warning-face)))
  "Face for warnings."
  :group 'fsharp-ui)

(defface fsharp-ui-error-face
  '((t (:inherit font-lock-error-face :underline t)))
  "Face for errors"
  :group 'fsharp-ui)

(defmacro def-fsharp-compiled-var (sym init &optional docstring)
  "Defines a SYMBOL as a constant inside an eval-and-compile form
with initial value INITVALUE and optional DOCSTRING."
  `(eval-and-compile
     (defvar ,sym ,init ,docstring)))

(def-fsharp-compiled-var fsharp-shebang-regexp
  "\\(^#!.*?\\)\\([A-Za-z0-9_-]+\\)$"
  "Capture the #! and path of a shebag in one group and the
  executable in another.")

(def-fsharp-compiled-var fsharp-access-control-regexp
  "private\\s-+\\|internal\\s-+\\|public\\s-+"
  "Match `private', `internal', or `public', followed by a space,
  with no capture.")

(def-fsharp-compiled-var fsharp-access-control-regexp-noncapturing
  (format "\\(?:%s\\)" fsharp-access-control-regexp)
  "Same as `fsharp-access-control-regexp', but captures")

(def-fsharp-compiled-var fsharp-inline-rec-regexp
  "inline\\s-+\\|rec\\s-+"
  "Match `inline' or `rec', followed by a space.")

(def-fsharp-compiled-var fsharp-inline-rec-regexp-noncapturing
  (format "\\(?:%s\\)" fsharp-inline-rec-regexp)
  "Match `inline' or `rec', followed by a space, with no capture.")

(def-fsharp-compiled-var fsharp-valid-identifier-regexp
  "[A-Za-z0-9_']+"
  "Match a normal, valid F# identifier -- alphanumeric characters
  plus ' and underbar. Does not capture")

(def-fsharp-compiled-var fsharp-function-def-regexp
  (concat "\\<\\(?:let\\|and\\|with\\)\\s-+"
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          (format "\\(%s\\)" fsharp-valid-identifier-regexp)
          "\\(?:\\s-+[A-Za-z_]\\|\\s-*(\\)" ;; matches function arguments or open-paren; unclear why 0-9 not in class
          ))

(def-fsharp-compiled-var fsharp-pattern-function-regexp
  (concat "\\<\\(?:let\\|and\\)\\s-+"
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          (format "\\(%s\\)" fsharp-valid-identifier-regexp)
          "\\s-*=\\s-*function")
  "Matches an implicit matcher, eg let foo m = function | \"cat\" -> etc.")

;; Note that this regexp is used for iMenu. To font-lock active patterns, we
;; need to use an anchored match in fsharp-font-lock-keywords.
(def-fsharp-compiled-var fsharp-active-pattern-regexp
  (concat "\\<\\(?:let\\|and\\)\\s-+"
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          "(\\(|[A-Za-z0-9_'|]+|\\))\\(?:\\s-+[A-Za-z_]\\|\\s-*(\\)"))

(def-fsharp-compiled-var fsharp-member-access-regexp
  "\\<\\(?:override\\|member\\|abstract\\)\\s-+"
  "Matches members declarations and modifiers on classes.")

(def-fsharp-compiled-var fsharp-member-function-regexp
  (concat fsharp-member-access-regexp
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          "\\(?:" fsharp-valid-identifier-regexp "\\.\\)?"
          "\\(" fsharp-valid-identifier-regexp "\\)")
  "Captures the final identifier in a member function declaration.")

(def-fsharp-compiled-var fsharp-overload-operator-regexp
  (concat fsharp-member-access-regexp
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          "\\(([!%&*+-./<=>?@^|~]+)\\)")
  "Match operators when overloaded by a type/class.")

(def-fsharp-compiled-var fsharp-constructor-regexp
  (concat "^\\s-*"
          fsharp-access-control-regexp-noncapturing "*"
          "\\<\\(new\\) *(.*)[^=]*=")
  "Matches the `new' keyword in a constructor")

(def-fsharp-compiled-var fsharp-type-def-regexp
  (concat "^\\s-*\\<\\(?:type\\|inherit\\)\\s-+"
          fsharp-access-control-regexp-noncapturing "*" ;; match access control 0 or more times
          "\\([A-Za-z0-9_'.]+\\)"))

(def-fsharp-compiled-var fsharp-var-or-arg-regexp
  "\\_<\\([A-Za-z_][A-Za-z0-9_']*\\)\\_>")

(def-fsharp-compiled-var fsharp-explicit-field-regexp
  (concat "^\\s-*\\(?:val\\|abstract\\)\\s-*\\(?:mutable\\s-+\\)?"
          fsharp-access-control-regexp-noncapturing "*" ;; match access control 0 or more times
          "\\([A-Za-z_][A-Za-z0-9_']*\\)\\s-*:\\s-*\\([A-Za-z_][A-Za-z0-9_'<> \t]*\\)"))

(def-fsharp-compiled-var fsharp-attributes-regexp
  "\\(\\[<[A-Za-z0-9_]+[( ]?\\)\\(\".*\"\\)?\\()?>\\]\\)"
  "Match attributes like [<EntryPoint>]; separately groups contained strings in attributes like [<Attribute(\"property\")>]")

;; F# makes extensive use of operators, many of which have some kind of
;; structural significance.
;;
;; In particular:
;; (| ... |)                 -- banana clips for Active Patterns (handled separately)
;; <@ ... @> and <@@ ... @@> -- quoted expressions
;; <| and |>                 -- left and right pipe (also <||, <|||, ||>, |||>)
;; << and >>                 -- function composition
;; |                         -- match / type expressions

(def-fsharp-compiled-var fsharp-operator-quote-regexp
  "\\(<@\\{1,2\\}\\)\\(?:.*\\)\\(@\\{1,2\\}>\\)"
  "Font lock <@/<@@ and @>/@@> operators.")

(def-fsharp-compiled-var fsharp-operator-pipe-regexp
  "<|\\{1,3\\}\\||\\{1,3\\}>"
  "Match the full range of pipe operators -- |>, ||>, |||>, etc.")

(def-fsharp-compiled-var fsharp-operator-case-regexp
  "\\s-+\\(|\\)[A-Za-z0-9_' ]"
  "Match literal | in contexts like match and type declarations.")

(defun fsharp-var-pre-form ()
  (save-excursion
    (re-search-forward "\\(:\\s-*\\w[^)]*\\)?=" nil t)
    (match-beginning 0)))

(defun fsharp-fun-pre-form ()
  (save-excursion
    (search-forward "->")))

;; Preprocessor directives (3.3)
(def-fsharp-compiled-var fsharp-ui-preproessor-directives
  '("#if" "#else" "#endif" "#light"))

;; Compiler directives (12.4)
(def-fsharp-compiled-var fsharp-ui-compiler-directives
  '("#nowarn" "#load" "#r" "#reference" "#I"
    "#Include" "#q" "#quit" "#time" "#help"))

;; Lexical matters (18.4)
(def-fsharp-compiled-var fsharp-ui-lexical-matters
  '("#indent"))

;; Line Directives (3.9)
(def-fsharp-compiled-var fsharp-ui-line-directives
  '("#line"))

;; Identifier replacements (3.11)
(def-fsharp-compiled-var fsharp-ui-identifier-replacements
  '("__SOURCE_DIRECTORY__" "__SOURCE_FILE__" "__LINE__"))

;; F# keywords (3.4)
(def-fsharp-compiled-var fsharp-ui-fsharp-threefour-keywords
  '("abstract" "and" "as" "assert" "base" "begin"
    "class" "default" "delegate" "do" "do!" "done"
    "downcast" "downto" "elif" "else" "end"
    "exception" "extern" "false" "finally" "for" "fun"
    "function" "global" "if" "in" "inherit" "inline"
    "interface" "internal" "lazy" "let" "let!"
    "match" "member" "module" "mutable" "namespace"
    "new" "not" "null" "of" "open" "or" "override"
    "private" "public" "rec" "return" "return!"
    "select" "static" "struct" "then" "to" "true"
    "try" "type" "upcast" "use" "use!"  "val" "void"
    "when" "while" "with" "yield" "yield!"))

;; "Reserved because they are reserved in OCaml"
(def-fsharp-compiled-var fsharp-ui-ocaml-reserved-words
  '("asr" "land" "lor" "lsl" "lsr" "lxor" "mod" "sig"))

;; F# reserved words for future use
(def-fsharp-compiled-var fsharp-ui-reserved-words
  '("atomic" "break" "checked" "component" "const"
    "constraint" "constructor" "continue" "eager"
    "event" "external" "fixed" "functor" "include"
    "method" "mixin" "object" "parallel" "process"
    "protected" "pure" "sealed" "tailcall" "trait"
    "virtual" "volatile"))

;; RMD 2016-09-30 -- This was pulled out separately with the following comment
;; when I got here. Not clear to me why it's on it's own, or even precisely what
;; the comment means. But: `async' is a valid F# keyword and needs to go someplace,
;; so I've left it here. For now.
;;
;; Workflows not yet handled by fsautocomplete but async
;; always present
(def-fsharp-compiled-var fsharp-ui-async-words
  '("async")
  "Just the word async, in a list.")

(def-fsharp-compiled-var fsharp-ui-word-list-regexp
  (regexp-opt
   `(,@fsharp-ui-async-words
     ,@fsharp-ui-compiler-directives
     ,@fsharp-ui-fsharp-threefour-keywords
     ,@fsharp-ui-identifier-replacements
     ,@fsharp-ui-lexical-matters
     ,@fsharp-ui-ocaml-reserved-words
     ,@fsharp-ui-preproessor-directives
     ,@fsharp-ui-reserved-words
     ,@fsharp-ui-line-directives)
   'symbols))

(defconst fsharp-font-lock-keywords
  (eval-when-compile
    `((,fsharp-ui-word-list-regexp 0 font-lock-keyword-face)
      ;; shebang
      (,fsharp-shebang-regexp
       (1 font-lock-comment-face)
       (2 font-lock-keyword-face))
      ;; attributes
      (,fsharp-attributes-regexp
       (1 font-lock-preprocessor-face)
       (2 font-lock-string-face nil t)
       (3 font-lock-preprocessor-face))
      ;; ;; type defines
      (,fsharp-type-def-regexp 1 font-lock-type-face)
      (,fsharp-function-def-regexp 1 font-lock-function-name-face)
      (,fsharp-pattern-function-regexp 1 font-lock-function-name-face)
      ;; Active Pattern
      ("(|" (0 'fsharp-ui-operator-face)
       ("\\([A-Za-z'_]+\\)\\(|)?\\)"
        nil nil
        (1 font-lock-function-name-face)
        (2 'fsharp-ui-operator-face)))
      (,fsharp-operator-pipe-regexp . 'fsharp-ui-operator-face)
      (,fsharp-member-function-regexp 1 font-lock-function-name-face)
      (,fsharp-overload-operator-regexp 1 font-lock-function-name-face)
      (,fsharp-constructor-regexp 1 font-lock-function-name-face)
      (,fsharp-operator-case-regexp 1 'fsharp-ui-operator-face)
      (,fsharp-operator-quote-regexp  (1 'fsharp-ui-operator-face)
                                      (2 'fsharp-ui-operator-face))
      ("[^:]:\\s-*\\(\\<[A-Za-z0-9_' ]*[^ ;\n,)}=<-]\\)\\(<[^>]*>\\)?"
       (1 font-lock-type-face)
       ;; 'prevent generic type arguments from being rendered in variable face
       (2 'fsharp-ui-generic-face nil t))
      (,(format "^\\s-*\\<\\(let\\|use\\|override\\|member\\|and\\|\\(?:%snew\\)\\)\\_>"
                (concat fsharp-access-control-regexp "*"))
       (0 font-lock-keyword-face) ; let binding and function arguments
       (,fsharp-var-or-arg-regexp
        (fsharp-var-pre-form) nil
        (1 font-lock-variable-name-face nil t)))
      ("\\<fun\\>"
       (0 font-lock-keyword-face) ; lambda function arguments
       (,fsharp-var-or-arg-regexp
        (fsharp-fun-pre-form) nil
        (1 font-lock-variable-name-face nil t)))
      (,fsharp-type-def-regexp
       (0 'font-lock-keyword-face) ; implicit constructor arguments
       (,fsharp-var-or-arg-regexp
        (fsharp-var-pre-form) nil
        (1 font-lock-variable-name-face nil t)))
      (,fsharp-explicit-field-regexp
       (1 font-lock-variable-name-face)
       (2 font-lock-type-face))

      ;; open namespace
      ("\\<open\s\\([A-Za-z0-9_.]+\\)" 1 font-lock-type-face)

      ;; module/namespace
      ("\\_<\\(?:module\\|namespace\\)\s\\([A-Za-z0-9_.]+\\)" 1 font-lock-type-face)
      )))

(defun fsharp-ui-setup-font-lock ()
  "Set up font locking for F# Mode."
  (setq font-lock-defaults
        '(fsharp-font-lock-keywords)))

(add-hook 'fsharp-mode-hook #'fsharp-ui-setup-font-lock)

(defun fsharp--syntax-propertize-function (start end)
  (goto-char start)
  (fsharp--syntax-string end)
  (funcall (syntax-propertize-rules
            ("\\(@\\)\"" (1 (prog1 "|" (fsharp--syntax-string end)))) ; verbatim string
            ("\\(\"\\)\"\"" (1 (prog1 "|" (fsharp--syntax-string end)))) ; triple-quoted string
            ("\\('\\)\\(?:[^\n\t\r\b\a\f\v\\\\]\\|\\\\[\"'ntrbafv\\\\]\\|\\\\u[0-9A-Fa-f]\\{4\\}\\|\\\\[0-9]\\{3\\}\\)\\('\\)"
             (1 "|") (2 "|")) ; character literal
            ("\\((\\)/" (1 "()"))
            ("\\(\(\\)\\*[!%&*+-\\./<=>@^|~?]*[\n\t\r\b\a\f\v ]*\)" (1 "()")) ; symbolic operator starting (* is not a comment
            ("\\(/\\)\\*" (1 ".")))
           start end))

(defun fsharp--syntax-string (end)
  (let* ((pst (syntax-ppss))
         (instr (nth 3 pst))
         (start (nth 8 pst)))
    (when (eq t instr) ; Then we are in a custom string
      (cond
       ((eq ?@ (char-after start)) ; Then we are in a verbatim string
        (while
            (when (re-search-forward "\"\"?" end 'move)
              (if (> (- (match-end 0) (match-beginning 0)) 1)
                  t ;; Skip this "" and keep looking further.
                (put-text-property (- (match-beginning 0) 1) (- (match-end 0) 1)
                                   'syntax-table (string-to-syntax "."))
                (put-text-property (match-beginning 0) (match-end 0)
                                   'syntax-table (string-to-syntax "|"))
                nil)))
        )

       (t ; Then we are in a triple-quoted string
        (when (re-search-forward "\"\"\"" end 'move)
          (put-text-property (- (match-beginning 0) 1) (match-beginning 0)
                             'syntax-table (string-to-syntax "."))
          (put-text-property (match-beginning 0) (match-end 0)
                             'syntax-table (string-to-syntax "|")))
        )))))


;;;###autoload
(define-derived-mode fsharp-mode prog-mode "fsharp"
  :group 'fsharp