~theo/fsharp-mode

55a10d2544a79aefe8628ba7c1212e5b706b373e — Theodor Thornhill 2 years ago 6804bef
Clean up even more
1 files changed, 86 insertions(+), 263 deletions(-)

M fsharp-mode.el
M fsharp-mode.el => fsharp-mode.el +86 -263
@@ 6,8 6,8 @@
;;         2010-2011 Laurent Le Brun <laurent@le-brun.eu>
;;         2012-2014 Robin Neatherway <robin.neatherway@gmail.com>
;;         2017-2019 Jürgen Hötzel
;; Maintainer: Jürgen Hötzel
;; Package-Requires: ((emacs "25")  (s "1.3.1") (dash "1.1.0") (eglot))
;;         2020      Theodor Thornhill
;; Maintainer: Theodor Thornhill <theo@thornhill.no>
;; Keywords: languages
;; Version: 1.9.15



@@ 31,7 31,6 @@
;;; Code:

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

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


@@ 190,187 189,123 @@ 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

(defconst 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
(defconst 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")
(defconst 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
(defconst 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)
(defconst 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
(defconst 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
(defconst 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)
          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
(defconst fsharp--pattern-function-regexp
  (concat "\\<\\(?:let\\|and\\)\\s-+"
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          (format "\\(%s\\)" fsharp-valid-identifier-regexp)
          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
(defconst fsharp--active-pattern-regexp
  (concat "\\<\\(?:let\\|and\\)\\s-+"
          fsharp-inline-rec-regexp-noncapturing "?"
          fsharp-access-control-regexp-noncapturing "*"
          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
(defconst 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 "\\)")
(defconst 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 "*"
(defconst 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
(defconst fsharp--constructor-regexp
  (concat "^\\s-*"
          fsharp-access-control-regexp-noncapturing "*"
          fsharp--access-control-regexp-noncapturing "*"
          "\\<\\(new\\) *(.*)[^=]*=")
  "Matches the `new' keyword in a constructor")

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

(def-fsharp-compiled-var fsharp-explicit-field-regexp
(defconst fsharp--explicit-field-regexp
  (concat "^\\s-*\\(?:val\\|abstract\\)\\s-*\\(?:mutable\\s-+\\)?"
          fsharp-access-control-regexp-noncapturing "*" ;; match access control 0 or more times
          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
(defconst 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
(defconst fsharp--operator-quote-regexp
  "\\(<@\\{1,2\\}\\)\\(?:.*\\)\\(@\\{1,2\\}>\\)"
  "Font lock <@/<@@ and @>/@@> operators.")

(def-fsharp-compiled-var fsharp-operator-pipe-regexp
(defconst 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"))
(defconst fsharp--regexp-type
  "\\b[A-Z][0-9A-Za-z_']*"
  "Regexp matching PascalCase types.")

;; Lexical matters (18.4)
(def-fsharp-compiled-var fsharp-ui-lexical-matters
  '("#indent"))
(defconst fsharp--regexp-delimiters
  (regexp-opt '("{" "}" "[" "]" "(" ")" "," "\\"))
  "Regexp matching delimiters.")

;; Line Directives (3.9)
(def-fsharp-compiled-var fsharp-ui-line-directives
  '("#line"))
(defconst fsharp--regexp-operators
  (regexp-opt '("+" "-" "*" "/" "|>" "<|" "->" ":"
                "|" "=" "==" "<" ">" ">=" "<="))
  "Regexp matching delimiters.")

;; 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
(defconst fsharp--reserved-keywords
  '("abstract" "and" "as" "assert" "base" "begin"
    "class" "default" "delegate" "do" "do!" "done"
    "downcast" "downto" "elif" "else" "end"


@@ 382,162 317,50 @@ with initial value INITVALUE and optional DOCSTRING."
    "private" "public" "rec" "return" "return!"
    "select" "static" "struct" "then" "to" "true"
    "try" "type" "upcast" "use" "use!"  "val" "void"
    "when" "while" "with" "yield" "yield!"))
    "when" "while" "with" "yield" "yield!" "async"

;; "Reserved because they are reserved in OCaml"
(def-fsharp-compiled-var fsharp-ui-ocaml-reserved-words
  '("asr" "land" "lor" "lsl" "lsr" "lxor" "mod" "sig"))
    "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"
    "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 "|")))
        )))))
    "virtual" "volatile")
  "Reserved keywords.")

(defconst fsharp--starter-syms
  '("open" "namespace" "module")
  "Keywords starting at indentation 0.")

(defconst fsharp--font-lock-keywords
  (append
   `(
     ;; Reserved keywords
     (,(regexp-opt fsharp--reserved-keywords 'symbols) . font-lock-keyword-face)

     ;; Function names
     (,fsharp--function-def-regexp . font-lock-function-name-face)

     ;; Types
     (,fsharp--regexp-type . font-lock-type-face)

     ;; Delimiters
     (,fsharp--regexp-delimiters . font-lock-negation-char-face)

     ;; Operators
     (,fsharp--regexp-operators . font-lock-variable-name-face)
     )))


;;;###autoload
(define-derived-mode fsharp-mode prog-mode "fsharp"
(define-derived-mode fsharp-mode prog-mode "F#"
  :group 'fsharp
  :syntax-table fsharp-mode-syntax-table
  "Major mode for editing fsharp code.

\\{fsharp-mode-map}"

  (require 'fsharp-mode-font)

  ;; ;; Movement
  ;; (setq-local beginning-of-defun-function #'elmo-beginning-of-defun)
  ;; (setq-local end-of-defun-function #'elmo-end-of-defun)


@@ 553,8 376,8 @@ with initial value INITVALUE and optional DOCSTRING."
  (setq-local comment-end "")

                                        ; Syntax highlighting
  (setq font-lock-defaults '(fsharp-font-lock-keywords))
  (setq syntax-propertize-function 'fsharp--syntax-propertize-function)
  (setq font-lock-defaults '(fsharp--font-lock-keywords
                             nil nil nil nil))

  (run-hooks 'fsharp-mode-hook))