├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── workflows │ └── test.yml ├── .gitignore ├── .travis.yml ├── Cask ├── Eask ├── Makefile ├── README.md ├── nim-capf.el ├── nim-compile.el ├── nim-fill.el ├── nim-helper.el ├── nim-mode.el ├── nim-rx.el ├── nim-smie.el ├── nim-suggest.el ├── nim-syntax.el ├── nim-util.el ├── nim-vars.el ├── starterKit.nims └── tests ├── helper └── test-syntax-helper.el ├── indents └── SMIE │ ├── &operater.nim │ ├── README.md │ ├── after-open-bracket.nim │ ├── after-open-paren.nim │ ├── after-open-slice.nim │ ├── block-break.nim │ ├── case-stmt.nim │ ├── combined-control-stmt.nim │ ├── comma.nim │ ├── comment.nim │ ├── concept.nim │ ├── const.nim │ ├── converter.nim │ ├── defer-with-comment.nim │ ├── defer.nim │ ├── do_notation.nim │ ├── empty_line.nim │ ├── enum.nim │ ├── for-break.nim │ ├── forward_declaration.nim │ ├── forward_declaration2.nim │ ├── if-stmt.nim │ ├── import.nim │ ├── infix_colon.nim │ ├── iter.nim │ ├── iterator.nim │ ├── iterator2.nim │ ├── let.nim │ ├── long-condition.nim │ ├── macro.nim │ ├── method.nim │ ├── nimble_file.nim │ ├── object-of.nim │ ├── object.nim │ ├── oneline_conditions.nim │ ├── paren-space.nim │ ├── proc.nim │ ├── proc_as_type.nim │ ├── raw │ └── inside_string.nim │ ├── semicolon.nim │ ├── static.nim │ ├── string.nim │ ├── template.nim │ ├── try-stmt.nim │ ├── tuple.nim │ ├── type.nim │ ├── using.nim │ ├── var.nim │ ├── when-stmt.nim │ ├── while-stmt.nim │ └── with-variable │ └── break.nim ├── syntax ├── backtick.nim ├── char.nim ├── docstring.nim ├── export.nim ├── format.nim ├── function_name.nim ├── multiline_comment.nim ├── number.nim ├── pragma.nim ├── single_comment.nims ├── string.nim ├── test_is_and_distinct.nim ├── test_nimscript.nims └── varargs.nim ├── test-smie-indent.el └── test-syntax.el /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Running Tests 2 | ============= 3 | 4 | You'll need [cask](https://github.com/cask/cask). Then run 5 | 6 | cask install 7 | 8 | for first time setup. For running the tests, use 9 | 10 | cask exec buttercup -L . 11 | 12 | Writing Tests 13 | ============= 14 | 15 | The tests are written with 16 | [buttercup](https://github.com/jorgenschaefer/emacs-buttercup/) Take a 17 | look at the existing tests. Currently, code samples for testing are in 18 | the `tests/samples/` subdirectory. The naming convention is 19 | `something-actual.nim` and `something-expected.nim`. 20 | 21 | Making New Issues 22 | ================= 23 | 24 | Most of `nim-mode`s features depend on nimsuggest. If you think your issue is 25 | related to nimsuggest, as a first debug step, see if the problem disappears when 26 | you disable nimsuggest. 27 | 28 | You can turn off nimsuggest by setting `nimsuggest-path` to `nil`: 29 | 30 | ```lisp 31 | ;; place this configuration in your .emacs or somewhere before emacs 32 | ;; load nim-mode and you may need to reboot your Emacs to check. 33 | (defconst nimsuggest-path nil) 34 | ``` 35 | 36 | `nim-mode` uses nimsuggest via `company-mode`, `eldoc-mode`, 37 | `flycheck-mode`, and `nimsuggest-find-definition` (goto definition). 38 | 39 | If you are completely new to Emacs, please check next section as well. 40 | 41 | Emacs Configuration or Debugging 42 | ================================ 43 | 44 | Some configuration variables are placed in nim-vars.el. Please take a look if 45 | you are interested. They should be available via customize-group. (indenting, 46 | faces, nimsuggest, etc.) 47 | 48 | If you new to Emacs, please visit [here](https://github.com/chrisdone/elisp-guide) 49 | and check `Evaluation` section (or other stuff). The C-x C-e or C-M-x 50 | is really convenient way to debug if you get errors in your configuration file. 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Conforming Bugs 2 | =============== 3 | 4 | Please tell us below: 5 | 6 | - [ ] basic information 7 | - OS name 8 | - Versions: Emacs, Nim compiler, and nimsuggest 9 | - [ ] Are you using latest version of nim-mode? 10 | - [ ] Are there message related to nim-mode in `*Messages*` buffer? 11 | - [ ] Did you read README.md? 12 | - [ ] Is your problem related to nimsuggest-mode? (company-mode, flycheck, or el-doc) 13 | - [ ] Did you try nim-mode without nimsugggest-mode? What happened? 14 | (Comment out nimsuggest-mode's config please) 15 | 16 | And describe your problem: 17 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | test: 16 | runs-on: ${{ matrix.os }} 17 | continue-on-error: ${{ matrix.experimental }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: [ubuntu-latest, macos-latest, windows-latest] 22 | emacs-version: 23 | - 26.3 24 | - 27.2 25 | - 28.2 26 | - 29.1 27 | experimental: [false] 28 | include: 29 | - os: ubuntu-latest 30 | emacs-version: snapshot 31 | experimental: true 32 | - os: macos-latest 33 | emacs-version: snapshot 34 | experimental: true 35 | - os: windows-latest 36 | emacs-version: snapshot 37 | experimental: true 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | 42 | - uses: jcs090218/setup-emacs@master 43 | with: 44 | version: ${{ matrix.emacs-version }} 45 | 46 | - uses: emacs-eask/setup-eask@master 47 | with: 48 | version: 'snapshot' 49 | 50 | - name: Run tests 51 | run: | 52 | make ci 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.elc 3 | .cask 4 | .eask 5 | /dist 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | sudo: no 3 | before_install: 4 | - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > x.sh && source ./x.sh 5 | 6 | install: 7 | - evm install $EVM_EMACS --use --skip 8 | 9 | env: 10 | - EVM_EMACS=emacs-24.4-travis 11 | - EVM_EMACS=emacs-24.5-travis 12 | - EVM_EMACS=emacs-25.1-travis 13 | - EVM_EMACS=emacs-25.2-travis 14 | - EVM_EMACS=emacs-25.3-travis 15 | - EVM_EMACS=remacs-git-snapshot-travis 16 | # - EVM_EMACS=emacs-git-snapshot-travis 17 | 18 | matrix: 19 | fast_finish: true 20 | allow_failures: 21 | env: 22 | - EVM_EMACS=emacs-git-snapshot-travis 23 | 24 | script: 25 | - emacs --version 26 | - EMACS=`which emacs` CASK_EMACS=$EMACS make test 27 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source melpa-stable) 2 | ;; Place GNU repository here to prior melpa-stable 3 | ;; repositry because company-mode includes test directory 4 | ;; and buttercup somehow executes the tests. 5 | (source gnu) 6 | 7 | (files "*.el") 8 | 9 | (development 10 | (depends-on "buttercup") 11 | (depends-on "epc") 12 | (depends-on "commenter") 13 | (depends-on "flycheck-nimsuggest") 14 | (depends-on "let-alist") 15 | (depends-on "cl-lib")) 16 | -------------------------------------------------------------------------------- /Eask: -------------------------------------------------------------------------------- 1 | (package "nim-mode" 2 | "0.4.2" 3 | "A major mode for the Nim programming language") 4 | 5 | (website-url "https://github.com/nim-lang/nim-mode") 6 | (keywords "nim" "languages") 7 | 8 | (package-file "nim-mode.el") 9 | 10 | (script "test" "echo \"Error: no test specified\" && exit 1") 11 | 12 | (source 'gnu) 13 | (source 'melpa) 14 | 15 | (depends-on "emacs" "24.4") 16 | (depends-on "epc" "0.1.1") 17 | (depends-on "let-alist" "1.0.1") 18 | (depends-on "commenter" "0.5.1") 19 | (depends-on "flycheck-nimsuggest" "0.8.1") 20 | 21 | (development 22 | (depends-on "buttercup") 23 | (depends-on "let-alist") 24 | (depends-on "cl-lib")) 25 | 26 | (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EASK ?= eask 2 | EMACS ?= emacs 3 | 4 | all: test 5 | 6 | # TODO: Add `test` when it's stable! 7 | ci: build compile 8 | 9 | build: 10 | ${EASK} package 11 | ${EASK} install 12 | 13 | test: 14 | ${EASK} install-deps --dev 15 | ${MAKE} compile 16 | ${MAKE} unit 17 | ${MAKE} clean-elc 18 | 19 | compile: 20 | ${EASK} compile 21 | 22 | unit: 23 | ${EASK} exec buttercup -L . 24 | 25 | clean-elc: 26 | ${EASK} clean elc 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `nim-mode` A major mode for editing Nim source code 2 | =================================================== 3 | [![Travis CI](https://travis-ci.org/nim-lang/nim-mode.svg?branch=master)](https://travis-ci.org/nim-lang/nim-mode) 4 | [![MELPA](http://melpa.org/packages/nim-mode-badge.svg)](http://melpa.org/#/nim-mode) 5 | [![MELPA Stable](http://stable.melpa.org/packages/nim-mode-badge.svg)](http://stable.melpa.org/#/nim-mode) 6 | 7 | This package provides (and requires Emacs 24.4 or higher version): 8 | 9 | - Syntax highlighting for ``*.nim``, ``*.nims``, ``*.nimble`` and 10 | ``nim.cfg`` files 11 | - `nim-compile` command (*C-c C-c*), with error matcher for the 12 | compile buffer 13 | - Nimsuggest (alpha): 14 | - on the fly linter using flycheck, or flymake (from Emacs 26) 15 | - auto-completion with company-mode ("C-M-i" for manual completion) 16 | - jump-to-definition (*M-.*, and *M-,* keys) 17 | - find-references (*M-?* key) 18 | - eldoc or help on hover in term of LSP 19 | - Automatic indentation and line breaking (alpha) 20 | - Outline by procedures (`hs-hide-all`, `hs-show-all` etc.) 21 | 22 | ## Installation 23 | 24 | * ensure packages from [MELPA](https://melpa.org/#/getting-started) 25 | can be installed 26 | * Install `nim-mode` (e.g. ``M-x package-install RET nim-mode RET``) 27 | 28 | ## nimsuggest (alpha) 29 | 30 | At the time of writing this it should be mentioned that both 31 | nimsuggest and nimsuggest-mode have problems that could cause emacs to 32 | be much less responsive, or even freeze. Apart from that is is non 33 | trivial to configure nimsuggest with the right parameters so that you 34 | also get correct results. So you have been warned. 35 | 36 | Nimsuggest is the compilation server for Nim, it runs in its own 37 | process, pretty much independent of emacs. ``nimsuggest-mode`` 38 | is an emacs minor mode that comes with ``nim-mode``. It is 39 | responsible to create the nimsuggest instance and connect emacs with it. 40 | ``nimsuggest-mode`` doesn't do anything visual in emacs yet. There are 41 | other minor modes such as ``flycheck``,``flymake`` (linting) and 42 | ``company`` (completion) that are responsible for editor integration. 43 | 44 | - ``flycheck`` and ``flymake`` are two alternative linting 45 | engines. Before emacs version ``26.1`` ``flymake`` was pretty much 46 | outdated and the recommended linting engine was the external 47 | ``flycheck``. But from version ``26.1`` onward, ``flymake`` is a 48 | good linting engine that comes with emacs. But you should not use both 49 | at the same time. 50 | - ``flycheck-nimsuggest`` is a backend for flycheck. It builds the bridge 51 | to ``nimsuggest-mode`` so that flycheck can visualize the linting 52 | information that nimsuggest provides. 53 | - ``flycheck-nim`` is an alternative backend for flycheck that does 54 | not interact with nimsuggest at all. Instead it uses the ``nim 55 | check`` command and parses the output of that command. 56 | - ``flymake-nimsuggest`` is the backend for ``flymake`` to build the 57 | bridge to ``nimsuggest-mode``. It comes with ``nim-mode``, and it is 58 | activated automatically in nim files, when ``flymake-mode`` is on. 59 | - ``company-mode`` is a minor mode for auto completion (company - complete anything) 60 | - ``company-nimsuggest`` is the backend for ``company-mode`` that 61 | builds the bridge to ``nimsuggest-mode``. 62 | - ``eldoc-mode`` is a minor mode for emacs that is responsible to show 63 | the documentation of emacs lisp symbols at point, hence the name. 64 | But ``eldoc-mode`` has been extended to work for 65 | other programming languages as well. ``nimsuggest-mode`` has 66 | integration for ``eldoc-mode`` so you can see documentation of nim 67 | symbols at point when ``nimsuggest-mode`` is active. 68 | 69 | For ``nimsuggest-mode`` to work, emacs needs to be able to find the nimsuggest binary, when it 70 | is on the path, it should just work, if not you can customize 71 | ``nimsuggest-path``. Since it is completely optional to use 72 | ``nimsuggest``, you have to activate ``nimsuggest-mode`` manually. 73 | 74 | ### Install nimsuggest 75 | 76 | 1. Use stable version: 77 | See [official download instruction](http://nim-lang.org/download.html) at 78 | "Installation based on generated C code" section. 79 | 80 | 2. Use latest version: 81 | This way may or may not work (depending on Nim or nimsuggest's 82 | state and we can't support all the way), so use above way 83 | if you prefer stable. 84 | ```sh 85 | # assuming you already installed Nim 86 | cd /path/to/Nim_repository 87 | ./koch tools 88 | ``` 89 | 90 | ### Keyboard Shortcuts (with nimsuggest) 91 | 92 | 1. Completion feature -- *C-M-i* and *M-TAB* keys and auto-complete feature if 93 | you install [company-mode](https://github.com/company-mode/company-mode) 94 | 2. Jump to Definition -- *M-.* to find the definition for symbol at 95 | poisition and *M-,* to go back. 96 | 3. Show Doc -- *C-c C-d* Show documentation of symbol at current 97 | position in the dedicated `*nim-doc*` buffer. 98 | 4. Show Short Doc -- (automatically) Shows the short documentation of the symbol at 99 | point in the minibuffer 100 | 101 | ## Grammar and Indentation 102 | 103 | In ``nim-smie.el`` there are nim grammar rules for ``smie`` 104 | (Simple Minded Indentation Engine). These rules give emacs a basic 105 | understanding of the Nim grammar. They are used to calculate a "correct" 106 | indentation level for code, and to fill (distribute line endings at 107 | margin) comments, multiline strings and other parts of the code. 108 | 109 | ``electric-indent-mode``, a global minor mode that is turned on by 110 | default, uses the rules from ``nim-smie.el`` to automatically reindent 111 | the current line, before a new line is started on *RET*. The rules 112 | sometimes can really make the emacs behave sluggish up to freezing for 113 | several seconds. The problem is most noticeable when the grammar gets 114 | confused with incomplete statements or the grammar becomes very 115 | uncommon through the usage of untyped macros for embedded domain 116 | language. Just as an example writing patterns for the nim library 117 | ``ast-pattern-matching`` really confuses smie and you might have to 118 | manually fix a lot of indentation that ``electric-indent-mode`` breaks 119 | automatically. 120 | 121 | My recommendation is to turn off electric indentation for Nim 122 | files. This can be done locally with 123 | ``(electric-indent-local-mode 0)``, or globally (not just Nim files) with 124 | ``(electric-indent-mode 0)``. Nim has semantic whitespace, therefore 125 | it might be better if the indentation is something that is inserted manually. 126 | 127 | ``auto-fill-mode``, a minor mode, uses the rules to break lines 128 | automatically. At the moment it is also not recommend to enable 129 | ``auto-fill-mode`` for Nim files. But using `fill-paragraph` (*M-q*) on 130 | comments does work reliably and it is very useful. 131 | 132 | ## Example Configuration 133 | 134 | You can copy and adjust the following configuration into your local 135 | `init.el` file. 136 | 137 | ```elisp 138 | ;; The `nimsuggest-path' will be set to the value of 139 | ;; (executable-find "nimsuggest"), automatically. 140 | (setq nimsuggest-path "path/to/nimsuggest") 141 | 142 | (defun my--init-nim-mode () 143 | "Local init function for `nim-mode'." 144 | 145 | ;; Just an example, by default these functions are 146 | ;; already mapped to "C-c <" and "C-c >". 147 | (local-set-key (kbd "M->") 'nim-indent-shift-right) 148 | (local-set-key (kbd "M-<") 'nim-indent-shift-left) 149 | 150 | ;; Make files in the nimble folder read only by default. 151 | ;; This can prevent to edit them by accident. 152 | (when (string-match "/\.nimble/" (or (buffer-file-name) "")) (read-only-mode 1)) 153 | 154 | ;; If you want to experiment, you can enable the following modes by 155 | ;; uncommenting their line. 156 | ;; (nimsuggest-mode 1) 157 | ;; Remember: Only enable either `flycheck-mode' or `flymake-mode' at the same time. 158 | ;; (flycheck-mode 1) 159 | ;; (flymake-mode 1) 160 | 161 | ;; The following modes are disabled for Nim files just for the case 162 | ;; that they are enabled globally. 163 | ;; Anything that is based on smie can cause problems. 164 | (auto-fill-mode 0) 165 | (electric-indent-local-mode 0) 166 | ) 167 | 168 | (add-hook 'nim-mode-hook 'my--init-nim-mode) 169 | 170 | ``` 171 | 172 | 173 | ## Other convenience packages for editing Nim source code 174 | 175 | Those packages are convenience packages and can be installed same way 176 | as nim-mode (M-x list-packages ...) 177 | 178 | - [indent-guide](https://github.com/zk-phi/indent-guide): show visible indent levels 179 | - [quickrun](https://github.com/syohex/emacs-quickrun): emacs port of vim's quickrun 180 | - [company-mode](https://github.com/company-mode/company-mode): auto-complete feature 181 | - [ob-nim](https://github.com/Lompik/ob-nim): org-mode integration focused on Nim 182 | - [wgrep](https://github.com/mhayashi1120/Emacs-wgrep): Writable grep buffer and apply the changes to files (maybe convenient for refactor stuff) 183 | - [suggestion-box-el](https://github.com/yuutayamada/suggestion-box-el): show argument info on the cursor 184 | 185 | ## Other editors/IDEs 186 | 187 | You can also find other editor/IDE plugins for 188 | Nim language [here](https://github.com/nim-lang/Nim/wiki/editor-support) 189 | -------------------------------------------------------------------------------- /nim-capf.el: -------------------------------------------------------------------------------- 1 | ;;; nim-capf.el --- Implementation of Completion At Point Function for nim-mode -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2016 Yuta Yamada 4 | 5 | ;; Author: Yuta Yamada 6 | ;; Keywords: completion 7 | 8 | ;; This package was made from company-nim.el and original authors were 9 | ;; Simon Hafner. 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | 26 | ;; This file provides Completion At Point Function future (capf in 27 | ;; short) and you can use it via C-M-i and M-TAB keys by Emacs' 28 | ;; default key bindings. 29 | ;; 30 | ;; Also capf allows you to work with company-mode without adding 31 | ;; backends. 32 | 33 | ;; For company-mode users: 34 | ;; You can configure minimum string length that company's auto-completion 35 | ;; starts by `company-minimum-prefix-length'. Also you can change the idle 36 | ;; time by `company-idle-delay'. 37 | 38 | ;; TODO: 39 | ;; - make sure with company-flx package (https://github.com/PythonNut/company-flx) 40 | ;; (currently somehow I couldn't use it) 41 | ;; 42 | ;;; Code: 43 | 44 | (require 'let-alist) 45 | (require 'nim-syntax) 46 | (require 'nim-suggest) 47 | (require 'nim-helper) 48 | 49 | (defcustom nim-capf--type-abbrevs 50 | ;; From ast.nim, some of them aren't used as completion maybe... 51 | '(("skUnknown" . "U") 52 | 53 | ("skConditional" . "") 54 | ("skDynLib" . "D") 55 | 56 | ("skParam" . "p") 57 | ("skGenericParam" . "P") ; 58 | ("skTemp" . "t") 59 | ("skModule" . ".") 60 | ("skType" . "T") 61 | ("skVar" . "V") 62 | ("skLet" . "L") 63 | ("skConst" . "C") 64 | ("skResult" . "r") 65 | ("skProc" . "f") 66 | ("skFunc" . "F") 67 | ("skMethod" . "m") 68 | ("skIterator" . "I") 69 | ("skConverter" . "c") 70 | ("skMacro" . "M") 71 | ("skTemplate" . "T") 72 | 73 | ("skField" . "Fi") 74 | ("skEnumField" . "en") 75 | ("skForVar" . "fv") 76 | ("skLabel" . "la") 77 | ("skStub" . "st") 78 | 79 | ("skPackage" . "P") 80 | ("skAlias" . "A") 81 | ) 82 | "Abbrevs for completion." 83 | :type 'assoc 84 | :group 'nim) 85 | 86 | (defun nim-capf--format-candidate (cand) 87 | "Put text property to CAND." 88 | (let ((qpath (nim--epc-qpath cand))) 89 | (propertize 90 | (car (last qpath)) 91 | :nim-line (nim--epc-line cand) 92 | :nim-column (nim--epc-column cand) 93 | :nim-type (nim--epc-forth cand) 94 | :nim-doc (nim--epc-doc cand) 95 | :nim-qpath qpath 96 | :nim-file (nim--epc-file cand) 97 | :nim-sk (nim--epc-symkind cand) 98 | :nim-sig (assoc-default 99 | (nim--epc-symkind cand) nim-capf--type-abbrevs) 100 | :nim-prefix (nim--epc-prefix cand)))) 101 | 102 | (defun nim-capf--format-candidates (_arg candidates) 103 | "Put text attributes to CANDIDATES." 104 | (mapcar #'nim-capf--format-candidate candidates)) 105 | 106 | (defun nim-capf--nimsuggest-async (prefix callback) 107 | "Query to nimsuggest asynchronously. 108 | 109 | The PREFIX is passed to the CALLBACK." 110 | ;; currently only support nim-mode (not nimscript-mode) 111 | (when (derived-mode-p 'nim-mode) 112 | (nimsuggest--call-epc 113 | 'sug 114 | (lambda (x) (funcall callback (nim-capf--format-candidates prefix x)))))) 115 | 116 | (defun nim-capf--prefix-p (beg end &optional skip) 117 | "Return t if completion should be triggered for prefix between BEG and END. 118 | If SKIP is non-nil, skip length check ." 119 | (and 120 | (if (or skip (eq this-command 'company-idle-begin) 121 | (eq ?. (char-before beg))) 122 | t 123 | (let ((diff (- end beg)) 124 | (len (bound-and-true-p company-minimum-prefix-length))) 125 | (and len (<= len diff)))) 126 | (<= beg end) 127 | (or (eolp) 128 | (let ((c-end (char-after end))) 129 | (and c-end (not (eq ?w (char-syntax c-end)))))))) 130 | 131 | (defun nim-capf--annotation (cand) 132 | "Get annotation info for CAND." 133 | (let ((ann (get-text-property 0 :nim-type cand)) 134 | (symbol (get-text-property 0 :nim-sig cand))) 135 | (format " %s [%s]" (substring ann 0 (cl-search "{" ann)) symbol))) 136 | 137 | (defun nim-capf--docsig (candidate) 138 | "Return meta/docsig information for company-mode of CANDIDATE." 139 | (apply 'nimsuggest--format 140 | (mapcar `(lambda (x) (get-text-property 0 x ,candidate)) 141 | '(:nim-type :nim-sk :nim-qpath :nim-doc)))) 142 | 143 | (defun nim-capf--location (cand) 144 | "Get location info for CAND." 145 | (let ((line (get-text-property 0 :nim-line cand)) 146 | (path (get-text-property 0 :nim-file cand))) 147 | (cons path line))) 148 | 149 | (defun nim-company--doc-buffer (candidate) 150 | "Get doc-buffer info for CANDIDATE." 151 | (when (fboundp 'company-doc-buffer) 152 | (let ((doc (get-text-property 0 :nim-doc candidate))) 153 | (unless (equal doc "") 154 | (funcall 'company-doc-buffer doc))))) 155 | 156 | ;;;###autoload 157 | (defun nim-capf-nimsuggest-completion-at-point () 158 | "Complete the symbol at point using nimsuggest." 159 | (when nimsuggest-mode 160 | (unless (nth 3 (syntax-ppss)) ;; not in string 161 | (let* ((bounds (bounds-of-thing-at-point 'symbol)) 162 | (beg (or (car bounds) (point))) 163 | (end (or (cdr bounds) (point))) 164 | (c-beg (char-after beg)) 165 | ;; avoid length check if previous char is "." 166 | (skip-len-check (and (not (bobp)) (eq ?. (char-before (point)))))) 167 | (list beg end 168 | (completion-table-with-cache 'nim-capf--nimsuggest-complete) 169 | ;; See `completion-extra-properties' for details 170 | :exit-function #'nim-capf--exit-function ; replacement of company's :post-completion 171 | :annotation-function #'nim-capf--annotation ; show annotation right after completion 172 | ;; default property of ‘completion-at-point-functions’ 173 | :exclusive 'no 174 | :predicate `(lambda (candidate) 175 | (if ,(and c-beg (< 65 c-beg 90)) ; whether A-Z 176 | (let ((thing (thing-at-point 'symbol))) 177 | (if thing 178 | ;; If user inputs capitalized string, 179 | ;; check only the first char. 180 | (eq ,c-beg (string-to-char candidate)) 181 | ;; let default predicate function 182 | t)) 183 | t)) 184 | ;; Company-mode integration 185 | ;; predicate by length 186 | :company-prefix-length (nim-capf--prefix-p beg end skip-len-check) 187 | ;; show something on minibuffer 188 | :company-docsig #'nim-capf--docsig 189 | ;; you can activate via F1 key, but currently no documentation available. 190 | :company-doc-buffer #'nim-company--doc-buffer 191 | ;; C-w key to open the source location 192 | :company-location #'nim-capf--location))))) 193 | 194 | (defun nim-capf--nimsuggest-complete (prefix) 195 | ;; Note this function is not async function 196 | "Completion symbol of PREFIX at point using nimsuggest." 197 | (unless (or (nim-inside-pragma-p) 198 | (nim-syntax-comment-or-string-p)) 199 | (cond 200 | ((or (string< "" prefix) 201 | (eq ?. (char-before (point)))) 202 | (nimsuggest--call-sync 203 | 'sug (lambda (args) (nim-capf--format-candidates prefix args))))))) 204 | 205 | (defun nim-capf--post-completion (candidate) 206 | "Post complete function based on CANDIDATE." 207 | (when-let* ((type-sig (get-text-property 0 :nim-sig candidate))) 208 | (cl-case (intern type-sig) 209 | ((f F m I c M T) 210 | (insert "()") 211 | (backward-char 1) 212 | (run-hook-with-args 'nim-capf-after-exit-function-hook candidate))))) 213 | 214 | (defun nim-capf--exit-function (str status) 215 | "Insert necessary things for STR, when completion is done. 216 | You may see information about STATUS at `completion-extra-properties'. 217 | But, for some reason, currently this future is only supporting 218 | company-mode. See also: https://github.com/company-mode/company-mode/issues/583" 219 | (unless (eq 'completion-at-point this-command) 220 | (cl-case status 221 | ;; finished -- completion was finished and there is no other completion 222 | ;; sole -- completion was finished and there is/are other completion(s) 223 | ((finished sole) 224 | (nim-capf--post-completion str)) 225 | (t 226 | ;; let other completion backends 227 | (setq this-command 'self-insert-command))))) 228 | 229 | ;; completion at point 230 | (defun nim-capf-builtin-completion () 231 | "This might not be precise, but maybe enough to someone." 232 | (eval-when-compile 233 | (append nim-keywords 234 | nim-types 235 | nim-exceptions 236 | nim-variables 237 | nim-constants 238 | nim-nonoverloadable-builtins 239 | nim-builtin-functions))) 240 | 241 | (defconst nim-capf-builtin-words 242 | (append (nim-capf-builtin-completion) 243 | nim-builtins-without-nimscript)) 244 | 245 | (defconst nim-capf-builtin-words-nimscript 246 | (append (nim-capf-builtin-completion) 247 | (append nimscript-builtins 248 | nimscript-variables))) 249 | 250 | (defvar nim-capf--pragma-words 251 | (eval-when-compile (cl-loop for (kwd . _) in nim-pragmas collect kwd)) 252 | "List of pragmas for `complietion-at-point-functions'.") 253 | 254 | (defun nim-capf--static-completion (words) 255 | "Return list of completion-at-point’s elements. 256 | List of WORDS are used as completion candidates." 257 | (when (or this-command (thing-at-point 'symbol)) 258 | (let* ((bounds (bounds-of-thing-at-point 'symbol)) 259 | (beg (or (car bounds) (point))) 260 | (end (or (cdr bounds) (point)))) 261 | (list beg end words 262 | :company-prefix-length (nim-capf--prefix-p beg end) 263 | :exclusive 'no)))) 264 | 265 | ;;;###autoload 266 | (defun nim-builtin-completion-at-point () 267 | "Complete the symbol at point for .nim files." 268 | (nim-capf--static-completion 269 | (if (nim-inside-pragma-p) 270 | nim-capf--pragma-words 271 | (append 272 | nim-capf-builtin-words 273 | ;; in string -- there are some env variable for unittest for nim 274 | ;; and I can't remember all the time. 275 | (when (nth 3 (syntax-ppss)) 276 | nim-environment-variables))))) 277 | 278 | ;;;###autoload 279 | (defun nimscript-builtin-completion-at-point () 280 | "Complete the symbol at point for nimscript files." 281 | (nim-capf--static-completion nim-capf-builtin-words-nimscript)) 282 | 283 | 284 | ;;; Company-mode integration 285 | (with-eval-after-load "company" 286 | (defun company-nimsuggest (command &optional arg &rest _args) 287 | "A function used to be as company-backend for `nim-mode'." 288 | (interactive (list 'interactive)) 289 | (cl-case command 290 | (interactive (company-begin-backend 'company-nimsuggest)) 291 | (candidates (cons :async (lambda (cb) (nim-capf--nimsuggest-async arg cb)))) 292 | (prefix (and 293 | (memq major-mode '(nim-mode nimscript-mode)) 294 | (not (or (nim-inside-pragma-p) 295 | (nim-syntax-comment-or-string-p))) 296 | (company-grab-symbol-cons "\\." 2))) 297 | (doc-buffer (nim-company--doc-buffer arg)) 298 | (annotation (nim-capf--annotation arg)) ; displayed on popup 299 | (meta (nim-capf--docsig arg)) ; displayed on minibuffer 300 | (location (nim-capf--location arg)) 301 | (post-completion (nim-capf--post-completion arg)) 302 | (sorted t)))) 303 | 304 | 305 | ;; Setup function 306 | 307 | ;;;###autoload 308 | (defun nim-capf-setup () 309 | "Setup." 310 | (let ((capf (cl-case major-mode 311 | (nim-mode 'nim-builtin-completion-at-point) 312 | (nimscript-mode 'nimscript-builtin-completion-at-point) 313 | (t (error "Unexpected major mode"))))) 314 | 315 | ;; Add builtin capf function (pragma and some keywords) 316 | (unless (memq capf completion-at-point-functions) 317 | (add-hook 'completion-at-point-functions 318 | capf nil 'local)) 319 | 320 | ;; if company-mode is disabled, just add nimsuggest's capf function. 321 | (unless (or (bound-and-true-p company-mode) 322 | (bound-and-true-p global-company-mode)) 323 | (unless (memq 'nim-capf-nimsuggest-completion-at-point completion-at-point-functions) 324 | (add-hook 'completion-at-point-functions 325 | 'nim-capf-nimsuggest-completion-at-point nil 'local))) 326 | 327 | ;; Add an asynchronous backend for company-mode. 328 | ;; The big difference between `company-nimsuggest' and 329 | ;; `nim-capf-nimsuggest-completion-at-point' is that company 330 | ;; version works with :async keyword. 331 | (when (bound-and-true-p company-backends) 332 | (add-to-list 'company-backends 'company-nimsuggest)))) 333 | 334 | ;;;###autoload 335 | (add-hook 'nimsuggest-mode-hook 'nim-capf-setup) 336 | 337 | 338 | ;; Suggestion-box-el 339 | ;; https://github.com/yuutayamada/suggestion-box-el 340 | (eval-after-load "suggestion-box" 341 | '(add-hook 'nim-capf-after-exit-function-hook 'suggestion-box-nim-by-type)) 342 | 343 | (provide 'nim-capf) 344 | 345 | ;; Local Variables: 346 | ;; coding: utf-8 347 | ;; mode: emacs-lisp 348 | ;; End: 349 | 350 | ;;; nim-capf.el ends here 351 | -------------------------------------------------------------------------------- /nim-compile.el: -------------------------------------------------------------------------------- 1 | ;;; nim-compile.el --- A support package of compilation for Nim -*- lexical-binding: t -*- 2 | 3 | ;;; Commentary: 4 | 5 | ;; TODO: compilation command is way too long. Maybe we should shorten 6 | ;; or hide some default options like excessiveStackTrace etc. 7 | 8 | ;;; Code: 9 | (require 'cl-lib) 10 | (require 'rx) 11 | (require 'nim-vars) 12 | 13 | (defvar nim-compile-command-checker-functions 14 | '(nim-compile--project) 15 | "Checker functions to decide build command. 16 | Functions (hooks) take one argument as file file string and 17 | return build command like ‘nim c -r FILE.nim’") 18 | 19 | (defvar nim-compile-default-command 20 | '("c" "-r" "--verbosity:0" "--hint[Processing]:off" "--excessiveStackTrace:on")) 21 | 22 | (defvar-local nim-compile--current-command nil) 23 | 24 | ;; MEMO: 25 | ;; Implemented based on compiler document: 26 | ;; http://nim-lang.org/docs/nimc.html#compiler-usage-configuration-files 27 | 28 | ;; (5) for PROJECT/PROJECT.(nim.cfg/.nims) 29 | (defun nim-get-project-file (candidates &optional dir) 30 | (let* ((projdir (file-name-base 31 | (directory-file-name (or dir default-directory)))) 32 | (projfile 33 | (directory-file-name (mapconcat 'file-name-as-directory 34 | `(,default-directory ,projdir) 35 | "")))) 36 | (cl-loop for ext in candidates 37 | for file = (format "%s%s" projfile ext) 38 | if (file-exists-p file) 39 | do (cl-return file)))) 40 | 41 | ;; (3,4) (parentDir or projectDir)/nim.cfg 42 | (defconst nim-config-regex 43 | (rx (group (or (group (or "nimcfg" "nim.cfg")) 44 | (group (? (and (0+ any) ".")) "nim.cfg")) 45 | line-end))) 46 | 47 | (defun nim-find-config-file () 48 | "Get the config file from current directory hierarchy. 49 | The config file would one of those: config.nims, PROJECT.nim.cfg, or nim.cfg." 50 | (nim-find-file-in-heirarchy 51 | (file-name-directory (buffer-file-name)) 52 | nim-config-regex)) 53 | 54 | (defun nim-find-file-in-heirarchy (current-dir pattern) 55 | "Search starting from CURRENT-DIR for a file matching PATTERN upwards through the directory hierarchy." 56 | (catch 'found 57 | (locate-dominating-file 58 | current-dir 59 | (lambda (dir) 60 | (let ((pfile (nim-get-project-file '(".nims" ".nim.cfg") dir))) 61 | (when pfile (throw 'found pfile))) 62 | (let ((file (cl-first (directory-files dir t pattern nil)))) 63 | (when file (throw 'found file))))))) 64 | 65 | (defcustom nim-project-root-regex "\\(\.git\\|\.nim\.cfg\\|\.nimble\\)$" 66 | "Regex to find project root directory." 67 | :type 'string 68 | :group 'nim) 69 | 70 | (defun nim-get-project-root () 71 | "Return project directory." 72 | (file-name-directory 73 | (nim-find-file-in-heirarchy 74 | (file-name-directory (buffer-file-name)) nim-project-root-regex))) 75 | 76 | ;; Compile command support 77 | (require 'compile) 78 | 79 | (defun nim-compile--project (file) 80 | "Return ‘nim build FILE’ if there is PROJECT.nims." 81 | (let ((proj (nim-get-project-file '(".nims" ".nim.cfg")))) 82 | (when (and proj (nim-nims-file-p proj) 83 | (eq major-mode 'nim-mode)) 84 | (nim--fmt '("build") file)))) 85 | 86 | (defun nim-nims-file-p (file) 87 | "Test if FILE is a nim script file." 88 | (equal "nims" (file-name-extension file))) 89 | 90 | (defun nim-nimble-file-p (file) 91 | "Test if FILE is a nimble file." 92 | (equal "nimble" (file-name-extension file))) 93 | 94 | (defun nim-compile--get-compile-command () 95 | "Return Nim's compile command or use previous command if it exists." 96 | (if nim-compile--current-command 97 | nim-compile--current-command 98 | (setq nim-compile--current-command 99 | (let ((file (when buffer-file-name 100 | buffer-file-name))) 101 | (when file 102 | (cond 103 | ;; WIP 104 | ((eq 'nimscript-mode major-mode) 105 | (let ((pfile (nim-get-project-file '(".nims" ".nimble")))) 106 | (cond 107 | ;; as build tool 108 | ((nim-nimble-file-p file) 109 | (let ((nim-compile-command "nimble")) 110 | (nim--fmt '("build") ""))) 111 | ((and (nim-nims-file-p pfile) 112 | (equal pfile buffer-file-name)) 113 | (nim--fmt '("build") pfile)) 114 | (t 115 | ;; as script file 116 | (nim--fmt '("e") file))))) 117 | (t 118 | (let ((cmd (run-hook-with-args-until-success 119 | 'nim-compile-command-checker-functions file))) 120 | (or cmd (nim--fmt nim-compile-default-command file)))))))))) 121 | 122 | (defun nim--fmt (args file) 123 | "Format ARGS and FILE for the nim command into a shell compatible string." 124 | (mapconcat 125 | 'shell-quote-argument 126 | (delq nil `(,nim-compile-command ,@args ,@nim-compile-user-args ,file)) 127 | " ")) 128 | 129 | (define-compilation-mode nim-compile-mode "nim-compile" 130 | "major-mode for *nim-compile* buffer." 131 | ;; keep `nim--colorize-compilation-buffer' for `recompile' function (g key) 132 | (if (eq major-mode 'nim-compile-mode) 133 | (add-hook 'compilation-filter-hook #'nim--colorize-compilation-buffer t) 134 | (remove-hook 'compilation-filter-hook #'nim--colorize-compilation-buffer t))) 135 | 136 | (defun nim-compile--assert (command) 137 | "Check COMMAND. 138 | Basically copied from `compile-command's document." 139 | (and (stringp command) 140 | (or (not (boundp (quote compilation-read-command))) compilation-read-command))) 141 | 142 | ;;;###autoload 143 | (defun nim-compile (&optional command) 144 | "Compile and execute the current buffer as a nim file. 145 | All output is written into the *nim-compile* buffer. 146 | If you put COMMAND argument, you can specify the compilation command." 147 | (interactive) 148 | (when (derived-mode-p 'nim-mode) 149 | (setq-local compile-command 150 | (or command 151 | (if (or compilation-read-command current-prefix-arg) 152 | (compilation-read-command (nim-compile--get-compile-command)) 153 | (nim-compile--get-compile-command)))) 154 | ;; keep users' previous command if they changed 155 | (setq-local nim-compile--current-command compile-command) 156 | (if (nim-compile--assert compile-command) 157 | (funcall 'compile compile-command 'nim-compile-mode) 158 | (error "Something goes wrong")))) 159 | 160 | 161 | ;; enable the regular for nim error messages in compilation buffers 162 | (add-to-list 'compilation-error-regexp-alist 'nim) 163 | 164 | ;; Define a regex to parse Nim's compilation message to jump over 165 | ;; error or warning points. 166 | (add-to-list 167 | 'compilation-error-regexp-alist-alist 168 | `(nim ,(rx line-start 169 | (or 170 | ;; Info 171 | (group-n 5 (or "Hint: " "template/generic instantiation from here")) 172 | ;; Warning or Error 173 | (group-n 7 174 | ;; File name 175 | (group-n 1 (1+ (in alnum "\\" "/" "_" "." "-") "") ".nim" (? "s")) 176 | "(" 177 | ;; Line 178 | (group-n 2 (1+ digit)) 179 | ;; Column -- this parameter is optional when it 180 | ;; comes from stacktrace (see #171) 181 | (? ", " (group-n 3 (1+ digit))) 182 | ")" 183 | ;; Type -- also this parameter doesn't show up when it 184 | ;; come from stacktrace 185 | " " (? (group-n 4 (or "Warning" "Error") ": ")))) 186 | ;; Capture rest of message 187 | (0+ any) line-end) 188 | ;; See `compilation-error-regexp-alist's document for the detail 189 | 1 2 3 (4 . 5))) 190 | 191 | (require 'ansi-color) 192 | (defun nim--colorize-compilation-buffer () 193 | "Colorize compilation buffer." 194 | (let ((inhibit-read-only t)) 195 | (ansi-color-apply-on-region compilation-filter-start (point-max)))) 196 | 197 | (provide 'nim-compile) 198 | ;;; nim-compile.el ends here 199 | -------------------------------------------------------------------------------- /nim-fill.el: -------------------------------------------------------------------------------- 1 | ;;; nim-fill.el --- -*- lexical-binding: t -*- 2 | 3 | ;;; Commentary: 4 | 5 | ;; 6 | 7 | ;;; Code: 8 | (require 'nim-smie) 9 | 10 | ;;; Fill Paragraph ;;; 11 | (defcustom nim-fill-comment-function 'nim-fill-comment 12 | "Function to fill comments. 13 | This is the function used by `nim-fill-paragraph' to 14 | fill comments." 15 | :type 'symbol 16 | :group 'nim) 17 | 18 | (defcustom nim-fill-string-function 'nim-fill-string 19 | "Function to fill strings. 20 | This is the function used by `nim-fill-paragraph' to 21 | fill strings." 22 | :type 'symbol 23 | :group 'nim) 24 | 25 | (defcustom nim-fill-decorator-function 'nim-fill-decorator 26 | "Function to fill decorators. 27 | This is the function used by `nim-fill-paragraph' to 28 | fill decorators." 29 | :type 'symbol 30 | :group 'nim) 31 | 32 | (defcustom nim-fill-paren-function 'nim-fill-paren 33 | "Function to fill parens. 34 | This is the function used by `nim-fill-paragraph' to 35 | fill parens." 36 | :type 'symbol 37 | :group 'nim) 38 | 39 | (defcustom nim-fill-docstring-style 'pep-257 40 | "Style used to fill docstrings. 41 | This affects `nim-fill-string' behavior with regards to 42 | triple quotes positioning. 43 | 44 | Possible values are `django', `onetwo', `pep-257', `pep-257-nn', 45 | `symmetric', and nil. A value of nil won't care about quotes 46 | position and will treat docstrings a normal string, any other 47 | value may result in one of the following docstring styles: 48 | 49 | `django': 50 | 51 | \"\"\" 52 | Process foo, return bar. 53 | \"\"\" 54 | 55 | \"\"\" 56 | Process foo, return bar. 57 | 58 | If processing fails throw ProcessingError. 59 | \"\"\" 60 | 61 | `onetwo': 62 | 63 | \"\"\"Process foo, return bar.\"\"\" 64 | 65 | \"\"\" 66 | Process foo, return bar. 67 | 68 | If processing fails throw ProcessingError. 69 | 70 | \"\"\" 71 | 72 | `pep-257': 73 | 74 | \"\"\"Process foo, return bar.\"\"\" 75 | 76 | \"\"\"Process foo, return bar. 77 | 78 | If processing fails throw ProcessingError. 79 | 80 | \"\"\" 81 | 82 | `pep-257-nn': 83 | 84 | \"\"\"Process foo, return bar.\"\"\" 85 | 86 | \"\"\"Process foo, return bar. 87 | 88 | If processing fails throw ProcessingError. 89 | \"\"\" 90 | 91 | `symmetric': 92 | 93 | \"\"\"Process foo, return bar.\"\"\" 94 | 95 | \"\"\" 96 | Process foo, return bar. 97 | 98 | If processing fails throw ProcessingError. 99 | \"\"\"" 100 | :type '(choice 101 | (const :tag "Don't format docstrings" nil) 102 | (const :tag "Django's coding standards style." django) 103 | (const :tag "One newline and start and Two at end style." onetwo) 104 | (const :tag "PEP-257 with 2 newlines at end of string." pep-257) 105 | (const :tag "PEP-257 with 1 newline at end of string." pep-257-nn) 106 | (const :tag "Symmetric style." symmetric)) 107 | :group 'nim 108 | :safe (lambda (val) 109 | (memq val '(django onetwo pep-257 pep-257-nn symmetric nil)))) 110 | 111 | (defun nim-fill-paragraph (&optional justify) 112 | "`fill-paragraph-function' handling multi-line strings and possibly comments. 113 | If any of the current line is in or at the end of a multi-line string, 114 | fill the string or the paragraph of it that point is in, preserving 115 | the string's indentation. 116 | Optional argument JUSTIFY defines if the paragraph should be justified." 117 | (interactive "P") 118 | (save-excursion 119 | (cond 120 | ;; Comments 121 | ((nim-syntax-context 'comment) 122 | (funcall nim-fill-comment-function justify)) 123 | ;; Strings/Docstrings 124 | ((save-excursion (or (nim-syntax-context 'string) 125 | (equal (string-to-syntax "|") 126 | (syntax-after (point))))) 127 | (funcall nim-fill-string-function justify)) 128 | ;; Decorators 129 | ((equal (char-after (save-excursion 130 | (nim-nav-beginning-of-statement))) ?@) 131 | (funcall nim-fill-decorator-function justify)) 132 | ;; Parens 133 | ((or (nim-syntax-context 'paren) 134 | (looking-at (nim-rx open-paren)) 135 | (save-excursion 136 | (skip-syntax-forward "^(" (line-end-position)) 137 | (looking-at (nim-rx open-paren)))) 138 | (funcall nim-fill-paren-function justify)) 139 | (t t)))) 140 | 141 | (defun nim-fill-comment (&optional justify) 142 | "Comment fill function for `nim-fill-paragraph'. 143 | JUSTIFY should be used (if applicable) as in `fill-paragraph'." 144 | (fill-comment-paragraph justify)) 145 | 146 | (defun nim-fill-string (&optional justify) 147 | "String fill function for `nim-fill-paragraph'. 148 | JUSTIFY should be used (if applicable) as in `fill-paragraph'." 149 | (let* ((str-start-pos 150 | (set-marker 151 | (make-marker) 152 | (or (nim-syntax-context 'string) 153 | (and (equal (string-to-syntax "|") 154 | (syntax-after (point))) 155 | (point))))) 156 | (num-quotes (nim-syntax-count-quotes 157 | (char-after str-start-pos) str-start-pos)) 158 | (str-end-pos 159 | (save-excursion 160 | (goto-char (+ str-start-pos num-quotes)) 161 | (or (re-search-forward (rx (syntax string-delimiter)) nil t) 162 | (goto-char (point-max))) 163 | (point-marker))) 164 | (multi-line-p 165 | ;; Docstring styles may vary for oneliners and multi-liners. 166 | (> (count-matches "\n" str-start-pos str-end-pos) 0)) 167 | (delimiters-style 168 | (pcase nim-fill-docstring-style 169 | ;; delimiters-style is a cons cell with the form 170 | ;; (START-NEWLINES . END-NEWLINES). When any of the sexps 171 | ;; is NIL means to not add any newlines for start or end 172 | ;; of docstring. See `nim-fill-docstring-style' for a 173 | ;; graphic idea of each style. 174 | (`django (cons 1 1)) 175 | (`onetwo (and multi-line-p (cons 1 2))) 176 | (`pep-257 (and multi-line-p (cons nil 2))) 177 | (`pep-257-nn (and multi-line-p (cons nil 1))) 178 | (`symmetric (and multi-line-p (cons 1 1))))) 179 | (fill-paragraph-function)) 180 | (save-restriction 181 | (narrow-to-region str-start-pos str-end-pos) 182 | (fill-paragraph justify)) 183 | (save-excursion 184 | (when (and (nim-docstring-p) nim-fill-docstring-style) 185 | ;; Add the number of newlines indicated by the selected style 186 | ;; at the start of the docstring. 187 | (goto-char (+ str-start-pos num-quotes)) 188 | (delete-region (point) (progn 189 | (skip-syntax-forward "> ") 190 | (point))) 191 | (and (car delimiters-style) 192 | (or (newline (car delimiters-style)) t) 193 | ;; Indent only if a newline is added. 194 | (indent-according-to-mode)) 195 | ;; Add the number of newlines indicated by the selected style 196 | ;; at the end of the docstring. 197 | (goto-char (if (not (= str-end-pos (point-max))) 198 | (- str-end-pos num-quotes) 199 | str-end-pos)) 200 | (delete-region (point) (progn 201 | (skip-syntax-backward "> ") 202 | (point))) 203 | (and (cdr delimiters-style) 204 | ;; Add newlines only if string ends. 205 | (not (= str-end-pos (point-max))) 206 | (or (newline (cdr delimiters-style)) t) 207 | ;; Again indent only if a newline is added. 208 | (indent-according-to-mode))))) t) 209 | 210 | (defun nim-fill-decorator (&optional _justify) 211 | "Decorator fill function for `nim-fill-paragraph'. 212 | JUSTIFY should be used (if applicable) as in `fill-paragraph'." 213 | t) 214 | 215 | (defun nim-fill-paren (&optional justify) 216 | "Paren fill function for `nim-fill-paragraph'. 217 | JUSTIFY should be used (if applicable) as in `fill-paragraph'." 218 | (save-restriction 219 | (narrow-to-region (progn 220 | (while (nim-syntax-context 'paren) 221 | (goto-char (1- (point)))) 222 | (line-beginning-position)) 223 | (progn 224 | (when (not (nim-syntax-context 'paren)) 225 | (end-of-line) 226 | (when (not (nim-syntax-context 'paren)) 227 | (skip-syntax-backward "^)"))) 228 | (while (and (nim-syntax-context 'paren) 229 | (not (eobp))) 230 | (goto-char (1+ (point)))) 231 | (point))) 232 | (let ((paragraph-start "\f\\|[ \t]*$") 233 | (paragraph-separate ",") 234 | (fill-paragraph-function)) 235 | (goto-char (point-min)) 236 | (fill-paragraph justify)) 237 | (while (not (eobp)) 238 | (forward-line 1) 239 | (nim--indent-line-core) 240 | (goto-char (line-end-position)))) 241 | t) 242 | 243 | (provide 'nim-fill) 244 | ;;; nim-fill.el ends here 245 | -------------------------------------------------------------------------------- /nim-mode.el: -------------------------------------------------------------------------------- 1 | ;;; nim-mode.el --- A major mode for the Nim programming language -*- lexical-binding: t -*- 2 | ;; 3 | ;; Filename: nim-mode.el 4 | ;; Description: A major mode for the Nim programming language 5 | ;; Author: Simon Hafner 6 | ;; Maintainer: Simon Hafner 7 | ;; URL: https://github.com/nim-lang/nim-mode 8 | ;; Version: 0.4.2 9 | ;; Keywords: nim languages 10 | ;; Compatibility: GNU Emacs 24.4 11 | ;; Package-Requires: ((emacs "24.4") (epc "0.1.1") (let-alist "1.0.1") (commenter "0.5.1") (flycheck-nimsuggest "0.8.1")) 12 | 13 | ;; Taken over from James H. Fisher 14 | ;; 15 | ;; This program is free software; you can redistribute it and/or modify 16 | ;; it under the terms of the GNU General Public License as published by 17 | ;; the Free Software Foundation; either version 2, or (at your option) 18 | ;; any later version. 19 | ;; 20 | ;; This program is distributed in the hope that it will be useful, 21 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | ;; GNU General Public License for more details. 24 | ;; 25 | ;; You should have received a copy of the GNU General Public License 26 | ;; along with this program; see the file COPYING. If not, write to 27 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth 28 | ;; Floor, Boston, MA 02110-1301, USA. 29 | ;; 30 | 31 | ;; Abstraction: 32 | ;; Large parts of this code is shamelessly stolen from python.el and 33 | ;; adapted to Nim. 34 | ;; 35 | ;; This package provide a major-mode future (syntax highlight and 36 | ;; indentation) and some necessity futures (jump-to-definition, 37 | ;; linting, el-doc) if you install nimsuggest. See below for the detail. 38 | ;; 39 | ;;; Commentary: 40 | ;; ## TL;DR 41 | 42 | ;; For regular Emacs users, all you need is below configuration in your 43 | ;; dot Emacs after you installed nim-mode from MELPA and nimsuggest 44 | ;; which you can make by `./koch tools` or `./koch nimsuggest`command in 45 | ;; the Nim repository (or check the official document on Nim website if 46 | ;; this information was outdated): 47 | ;; 48 | ;; Activate nimsuggest dedicated mode on `nim-mode': 49 | ;; 50 | ;; (add-hook 'nim-mode-hook 'nimsuggest-mode) 51 | ;; 52 | ;; Below configs are can be optional 53 | ;; 54 | ;; The `nimsuggest-path' will be set the value of (executable-find "nimsuggest") 55 | ;; automatically. 56 | ;; 57 | ;; (setq nimsuggest-path "path/to/nimsuggest") 58 | ;; 59 | ;; You may need to install below packages if you haven't installed yet. 60 | ;; 61 | ;; -- Auto completion -- 62 | ;; You can omit if you configured company-mode on 'prog-mode-hook 63 | ;; (add-hook 'nimsuggest-mode-hook 'company-mode) ; auto complete package 64 | ;; 65 | ;; -- Auto lint -- 66 | ;; You can omit if you configured flycheck-mode on 'prog-mode-hook 67 | ;; 68 | ;; (add-hook 'nimsuggest-mode-hook 'flycheck-mode) ; auto linter package 69 | ;; 70 | ;; See more information at https://github.com/nim-lang/nim-mode 71 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 72 | ;; 73 | ;;; Code: 74 | 75 | ;; To update some autoloads on local for `el-get' 76 | ;; (el-get-update-autoloads 'nim-mode) 77 | 78 | (require 'cl-lib) 79 | 80 | ;; Order of loading 81 | (require 'nim-vars) 82 | (require 'nim-rx) 83 | (require 'nim-syntax) 84 | (require 'nim-util) 85 | (require 'nim-helper) 86 | (require 'nim-smie) 87 | (require 'paren) ; for ‘show-paren-data-function’ 88 | (require 'nim-fill) 89 | (require 'commenter) 90 | 91 | (put 'nim-mode 'font-lock-defaults '(nim-font-lock-keywords nil t)) 92 | 93 | (defun nim-font-lock-syntactic-face-function (syntax-ppss) 94 | "Return syntactic face given SYNTAX-PPSS." 95 | (if (nth 4 syntax-ppss) ; if nth 4 is exist, it means inside comment. 96 | (if (nim-docstring-p syntax-ppss) 97 | font-lock-doc-face 98 | font-lock-comment-face) 99 | font-lock-string-face)) 100 | 101 | (defun nim--common-init () 102 | "Common configuration for ‘nim-mode’ and ‘nimscript-mode’." 103 | (run-hooks 'nim-common-init-hook) 104 | 105 | (setq-local nim--inside-compiler-dir-p 106 | (when (and buffer-file-name 107 | (string-match 108 | nimsuggest-ignore-dir-regex buffer-file-name)) 109 | t)) 110 | 111 | ;; Comment 112 | (setq-local comment-style 'indent) 113 | (setq-local comment-use-syntax t) 114 | ;; Those start and end comment variables are for initial value. 115 | (setq-local comment-start "#") 116 | (setq-local comment-end "") 117 | ;; actual comment values are defined here 118 | (setq-local commenter-config nim-comment) 119 | (commenter-setup) 120 | 121 | ;; ElDoc 122 | ;; See `eldoc-documentation-function'. Don't move this function out 123 | ;; side of major-mode function; it may cause weird eldoc behavior 124 | ;; (`eldoc-documentation-function' was set, but eldoc doesn't start 125 | ;; only first time you open a nim file) 126 | (nim-eldoc-on) 127 | 128 | ;; SMIE 129 | (smie-setup nim-mode-smie-grammar 'nim-mode-smie-rules 130 | :forward-token 'nim-mode-forward-token 131 | :backward-token 'nim-mode-backward-token) 132 | (setq-local indent-line-function #'nim-indent-line) 133 | ;; FIXME: due to uncompleted Nim’s smie grammar, 134 | ;; ‘smie--matching-block-data’ function gets stop when 135 | ;; the cursor is at proc/template/macro to find terminator 136 | ;; (I guess). To prevent this, temporary use default 137 | ;; show-paren-mode’s function instead. 138 | (setq-local show-paren-data-function #'show-paren--default) 139 | ;; Work around for #111 140 | (remove-hook 'post-self-insert-hook 'smie-blink-matching-open t) 141 | 142 | ;; Always indent with SPACES! 143 | (setq-local indent-tabs-mode nil) 144 | (setq-local parse-sexp-lookup-properties t) 145 | (setq-local parse-sexp-ignore-comments t) 146 | 147 | ;; Syntax highlight for strings 148 | (setq-local syntax-propertize-function nim-syntax-propertize-function) 149 | 150 | ;; Paragraph 151 | (setq-local paragraph-start "\\s-*$") 152 | ;; Navigation 153 | (setq-local beginning-of-defun-function #'nim-nav-beginning-of-defun) ; C-M-a 154 | (setq-local end-of-defun-function #'nim-nav-end-of-defun) ; C-M-e 155 | ;; Fill 156 | (setq-local fill-paragraph-function #'nim-fill-paragraph) 157 | ;; add-log 158 | (setq-local add-log-current-defun-function #'nim-info-current-defun) 159 | 160 | ;; Hooks 161 | ;; Add """ ... """ pairing to electric-pair-mode. 162 | (add-hook 'post-self-insert-hook 163 | #'nim-electric-pair-string-delimiter 'append t) 164 | (add-hook 'post-self-insert-hook 165 | #'nim-indent-post-self-insert-function 'append 'local) 166 | (add-hook 'which-func-functions #'nim-info-current-defun nil t) 167 | 168 | ;; Workaround with org comments in order to properly 169 | ;; detect opening #[ and closing comments ]# 170 | (when (and (derived-mode-p 'org-mode) 171 | (fboundp 'org-in-src-block-p) (org-in-src-block-p)) 172 | (modify-syntax-entry ?# ". 124b" nim-mode-syntax-table) 173 | (modify-syntax-entry ?\[ ". 23" nim-mode-syntax-table)) 174 | 175 | ;; Because indentation is not redundant, we cannot safely reindent code. 176 | (setq-local electric-indent-inhibit t) 177 | 178 | ;; Use user configuration of `electric-indent-chars' mainly to use C-m/Return 179 | ;; key as `newline-and-indent'. If you prefer Emacs' old behavior regarding 180 | ;; C-m and C-j, you can configure like this: 181 | ;; 182 | ;; (setq electric-indent-chars '()) 183 | ;; (global-set-key (kbd "C-j") #'newline-and-indent) 184 | (setq-local electric-indent-chars (append electric-indent-chars '(?: ?\s))) 185 | (when electric-indent-mode 186 | (define-key nim-mode-map [remap delete-backward-char] #'nim-electric-backspace))) 187 | 188 | ;; add ‘nim-indent-function’ to electric-indent’s 189 | ;; blocklist. ‘electric-indent-inhibit’ isn’t enough for old emacs. 190 | (add-to-list 'electric-indent-functions-without-reindent 'nim-indent-line) 191 | 192 | ;;;###autoload 193 | (define-derived-mode nim-mode prog-mode "Nim" 194 | "A major mode for the Nim programming language." 195 | :group 'nim 196 | 197 | ;; init hook 198 | (run-hooks 'nim-mode-init-hook) 199 | 200 | (nim--common-init) 201 | 202 | ;; Font lock 203 | (nim--set-font-lock-keywords 'nim-mode)) 204 | 205 | ;;; NimScript 206 | ;; -- https://nim-lang.org/docs/nims.html 207 | ;; -- https://github.com/nim-lang/Nim/wiki/Using-nimscript-for-configuration 208 | 209 | ;;;###autoload 210 | (define-derived-mode nimscript-mode prog-mode "NimScript" 211 | "A major-mode for NimScript files. 212 | This major-mode is activated when you enter *.nims and *.nimble 213 | suffixed files, but if it’s .nimble file, also another logic is 214 | applied. See also ‘nimscript-mode-maybe’." 215 | :group 'nim 216 | 217 | (nim--common-init) 218 | 219 | (nim--set-font-lock-keywords 'nimscript-mode)) 220 | 221 | ;;;###autoload 222 | (defun nimscript-mode-maybe () 223 | "Most likely turn on ‘nimscript-mode’. 224 | In *.nimble files, if the first line sentence matches 225 | ‘nim-nimble-ini-format-regex’, this function activates ‘conf-mode’ 226 | instead. The default regex’s matching word is [Package]." 227 | (interactive) 228 | (if (not (buffer-file-name)) 229 | (nimscript-mode) 230 | (let ((extension (file-name-extension (buffer-file-name)))) 231 | (cond ((equal "nims" extension) 232 | (nimscript-mode)) 233 | ((equal "nimble" extension) 234 | (save-excursion 235 | (goto-char (point-min)) 236 | (if (looking-at-p nim-nimble-ini-format-regex) 237 | (conf-mode) 238 | (nimscript-mode)))))))) 239 | 240 | ;;; `auto-mode-alist' 241 | ;;;###autoload 242 | (add-to-list 'auto-mode-alist '("\\.nim\\'" . nim-mode)) 243 | ;;;###autoload 244 | (add-to-list 'auto-mode-alist '("\\.nim\\(ble\\|s\\)\\'" . nimscript-mode-maybe)) 245 | 246 | 247 | ;;; Font locks 248 | (defun nim--set-font-lock-keywords (mode &optional arg) 249 | (let ((keywords 250 | (cl-case mode 251 | (nim-mode 252 | (cl-typecase (or arg font-lock-maximum-decoration) 253 | (null (nim--get-font-lock-keywords 0)) 254 | (list 255 | (nim--get-font-lock-keywords 256 | (or (assoc-default 'nim-mode font-lock-maximum-decoration) 257 | (assoc-default t font-lock-maximum-decoration) 258 | t))) 259 | (number (nim--get-font-lock-keywords font-lock-maximum-decoration)) 260 | (t (nim--get-font-lock-keywords t)))) 261 | (nimscript-mode 262 | (append nim-font-lock-keywords 263 | nim-font-lock-keywords-extra 264 | nim-font-lock-keywords-2 265 | ;; Add extra keywords for NimScript 266 | nimscript-keywords))))) 267 | (setq-local font-lock-defaults 268 | `(,keywords 269 | nil nil nil nil 270 | (font-lock-syntactic-face-function 271 | . nim-font-lock-syntactic-face-function))))) 272 | 273 | (defun nim--get-font-lock-keywords (level) 274 | "Return font lock keywords, according to ‘font-lock-maximum-decoration’ LEVEL. 275 | 276 | You can set below values as LEVEL: 277 | 278 | 0 or nil - only comment and string will be highlighted 279 | 1 - only basic keywords like if, or when 280 | 2 - don’t highlight some extra highlights 281 | t - default 282 | 283 | Note that without above values will be treated as t." 284 | (cl-case level 285 | (0 nil) 286 | (1 nim-font-lock-keywords) 287 | (2 (append nim-font-lock-keywords 288 | nim-font-lock-keywords-2 289 | nim-font-lock-keywords-3)) 290 | (t (append nim-font-lock-keywords 291 | nim-font-lock-keywords-extra 292 | nim-font-lock-keywords-2 293 | nim-font-lock-keywords-3)))) 294 | 295 | 296 | ;; Electric 297 | ;; (https://www.emacswiki.org/emacs/Electricity) 298 | 299 | (defun nim-indent-post-self-insert-function () 300 | "Adjust indentation after insertion of some characters. 301 | This function is intended to be added to `post-self-insert-hook.' 302 | If a line renders a paren alone, after adding a char before it, 303 | the line will be re-indented automatically if needed." 304 | (when (and electric-indent-mode 305 | (eq (char-before) last-command-event)) 306 | (cond 307 | ;; Electric indent inside parens 308 | ((and 309 | (not (bolp)) 310 | (let ((paren-start (nim-syntax-context 'paren))) 311 | ;; Check that point is inside parens. 312 | (when paren-start 313 | (not 314 | ;; Filter the case where input is happening in the same 315 | ;; line where the open paren is. 316 | (= (line-number-at-pos) 317 | (line-number-at-pos paren-start))))) 318 | ;; When content has been added before the closing paren or a 319 | ;; comma has been inserted, it's ok to do the trick. 320 | (or 321 | (memq (char-after) '(?\) ?\] ?\})) 322 | (eq (char-before) ?,))) 323 | (save-excursion 324 | (goto-char (line-beginning-position)) 325 | (let ((indentation (nim-indent-calculate-indentation))) 326 | (when (and (numberp indentation) (< (current-indentation) indentation)) 327 | (indent-line-to indentation))))) 328 | ;; Electric colon 329 | (t 330 | (let ((char last-command-event)) 331 | (when (and (memq char electric-indent-chars) 332 | (not (nim-syntax-comment-or-string-p))) 333 | (cl-case char 334 | (?: (nim-electric-colon)) 335 | (?\s (nim-electric-space))))))))) 336 | 337 | (defun nim-electric-colon () 338 | (when (and (not current-prefix-arg) 339 | ;; Trigger electric colon only at end of line 340 | (eolp) 341 | ;; Avoid re-indenting on extra colon 342 | (not (equal ?: (char-before (1- (point)))))) 343 | ;; Just re-indent dedenters 344 | (let ((dedenter-pos (nim-info-dedenter-statement-p)) 345 | (current-pos (point))) 346 | (when dedenter-pos 347 | (save-excursion 348 | (goto-char dedenter-pos) 349 | (nim--indent-line-core) 350 | (unless (= (line-number-at-pos dedenter-pos) 351 | (line-number-at-pos current-pos)) 352 | ;; Reindent region if this is a multiline statement 353 | (indent-region dedenter-pos current-pos))))))) 354 | 355 | (defun nim-electric-space () 356 | (let (next) 357 | (when (and 358 | (eq (current-indentation) (current-column)) 359 | (looking-back "^ +" (point-at-bol)) 360 | (cl-oddp (current-indentation)) 361 | (let* ((levels (nim-indent-calculate-levels)) 362 | (next-indent (cadr (member (1- (current-indentation)) levels)))) 363 | (prog1 (and next-indent (< (current-indentation) next-indent)) 364 | (setq next next-indent)))) 365 | (indent-line-to next)))) 366 | 367 | (defun nim-electric-backspace (&rest args) 368 | "Delete preceding char or levels of indentation. 369 | The ARGS are passed to original ‘delete-backward-char’ function." 370 | (interactive "p\nP") 371 | (let (back) 372 | (if (and electric-indent-mode 373 | (eq (current-indentation) (current-column)) 374 | (called-interactively-p 'interactive) 375 | (not (nim-syntax-comment-or-string-p)) 376 | (not (bolp)) 377 | (not current-prefix-arg) 378 | (let ((levels (reverse (nim-indent-calculate-levels)))) 379 | (setq back (cadr (member (current-indentation) levels))))) 380 | (indent-line-to back) 381 | (apply 'delete-backward-char args)))) 382 | 383 | ;;; ELDOC 384 | 385 | ;; Eldoc supports for Nim's static information like pragma. 386 | ;; When you activate nimsuggest-mode, this also support's 387 | ;; nimsuggest's eldoc information. 388 | 389 | (defvar nim-eldoc--skip-regex 390 | (rx (or (group symbol-start 391 | (or "if" "when" "elif" "while" 392 | ;; for tuple assignment 393 | "var" "let" "const") 394 | symbol-end (0+ " ")) 395 | (group line-start (0+ " "))))) 396 | 397 | (defun nim-eldoc-on-p() 398 | "Return non-nil if eldoc mode is activated." 399 | (or (bound-and-true-p eldoc-mode) 400 | ;; This mode was added at Emacs 25 401 | (bound-and-true-p global-eldoc-mode))) 402 | 403 | (defun nim-eldoc--try-p () 404 | "Return non-nil if current position can check eldoc." 405 | (and (nim-eldoc-on-p) 406 | (not (nim-line-comment-p)) 407 | (not (member (char-after (point)) 408 | ;; not sure this works on windows 409 | '(?\s ?\n))))) 410 | 411 | ;;;###autoload 412 | (defun nim-eldoc-function () 413 | "Return a doc string appropriate for the current context, or nil." 414 | (interactive) 415 | (when (nim-eldoc--try-p) 416 | (if (nim-inside-pragma-p) 417 | (nim-eldoc--pragma-at-point) 418 | (funcall nimsuggest-eldoc-function)))) 419 | 420 | ;;;###autoload 421 | (defun nim-eldoc-on () 422 | "This may or may not work. Maybe this configuration has to set on. 423 | Major-mode configuration according to the document." 424 | (interactive) 425 | (add-function :before-until (local 'eldoc-documentation-function) 426 | 'nim-eldoc-function)) 427 | 428 | (defun nim-eldoc-off () 429 | "Turn off nim eldoc mode." 430 | (interactive) 431 | (remove-function (local 'eldoc-documentation-function) 'nim-eldoc-function)) 432 | 433 | ;;;###autoload 434 | (defun nim-eldoc-setup (&rest _args) 435 | "This function may not work. 436 | I tried to configure this stuff to be user definable, but currently failing. 437 | The eldoc support should be turned on automatically, so please 438 | use `nim-eldoc-off' manually if you don't like it." 439 | ;; TODO: describe not to support for manual configuration? 440 | ;; see also `eldoc-documentation-function'. It implies that the 441 | ;; configure should be done by major-mode side, so might be 442 | ;; impossible to configure by hook? 443 | (if (nim-eldoc-on-p) (nim-eldoc-on) (nim-eldoc-off))) 444 | 445 | (defun nim-eldoc--get-pragma (pragma) 446 | "Get the PRAGMA's doc string." 447 | (let ((data (assoc-default pragma nim-pragmas))) 448 | (cl-typecase data 449 | (string data) 450 | ;; FIXME: more better operation 451 | (list (car data))))) 452 | 453 | (defun nim-eldoc--pragma-at-point () 454 | "Return string of pragma's description at point." 455 | (let* ((thing (thing-at-point 'symbol)) 456 | (desc (nim-eldoc--get-pragma thing))) 457 | (when (and desc (string< "" desc)) 458 | (format "%s: %s" thing (nim-eldoc--get-pragma thing))))) 459 | 460 | (defun nim-eldoc-inside-paren-p () 461 | "Return non-nil if it's inside pragma." 462 | (save-excursion 463 | (let ((ppss (syntax-ppss))) 464 | (and (< 0 (nth 0 ppss)) 465 | (eq ?\( (char-after (nth 1 ppss))))))) 466 | 467 | ;; backward compatibility 468 | (defalias 'nim-eldoc-setup 'ignore) 469 | 470 | 471 | ;; hideshow.el (hs-minor-mode) 472 | (defun nim-hideshow-forward-sexp-function (_arg) 473 | "Nim specific `forward-sexp' function for `hs-minor-mode'. 474 | Argument ARG is ignored." 475 | (nim-nav-end-of-defun) 476 | (unless (nim-line-empty-p) 477 | (backward-char))) 478 | 479 | (add-to-list 480 | 'hs-special-modes-alist 481 | `(nim-mode 482 | ,nim-nav-beginning-of-defun-regexp 483 | ;; Use the empty string as end regexp so it doesn't default to 484 | ;; "\\s)". This way parens at end of defun are properly hidden. 485 | "" 486 | "#" 487 | nim-hideshow-forward-sexp-function 488 | nil)) 489 | 490 | (provide 'nim-mode) 491 | 492 | ;;; nim-mode.el ends here 493 | -------------------------------------------------------------------------------- /nim-rx.el: -------------------------------------------------------------------------------- 1 | ;;; nim-rx.el --- -*- lexical-binding: t -*- 2 | 3 | ;; This program is free software; you can redistribute it and/or modify 4 | ;; it under the terms of the GNU General Public License as published by 5 | ;; the Free Software Foundation, either version 3 of the License, or 6 | ;; (at your option) any later version. 7 | 8 | ;; This program is distributed in the hope that it will be useful, 9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | ;; GNU General Public License for more details. 12 | 13 | ;; You should have received a copy of the GNU General Public License 14 | ;; along with this program. If not, see . 15 | 16 | ;;; Commentary: 17 | 18 | ;; 19 | 20 | ;;; Code: 21 | 22 | (require 'rx) 23 | (require 'cl-lib) 24 | (require 'nim-vars) 25 | 26 | (defvar nim-rx-constituents 27 | (eval-when-compile 28 | (let* ((constituents1 29 | (cl-loop for (sym . kwd) in `((dedenter . ("elif" "else" "of" "except" "finally")) 30 | (defun . ("proc" "func" "method" "converter" "iterator" "template" "macro")) 31 | (block-start-defun . ("proc" "func" "method" "converter" "iterator" 32 | "template" "macro" 33 | "if" "elif" "else" "when" "while" "for" "case" "of" 34 | "try" "except" "finally" 35 | "with" "block" 36 | "enum" "tuple" "object"))) 37 | collect (cons sym (apply 38 | (lambda (kwd) 39 | (eval `(rx symbol-start (or ,@kwd) symbol-end))) 40 | (list kwd))))) 41 | (constituents2 `((decl-block . ,(rx symbol-start 42 | (or "type" "const" "var" "let" "import") 43 | symbol-end 44 | (* space) 45 | (or "#" eol))) 46 | (symbol-name . ,(rx (any letter ?_ ?–) (* (any word ?_ ?–)))) 47 | (hex-lit . ,(rx "0" (or "x" "X") xdigit (0+ (or xdigit "_")))) 48 | (dec-lit . ,(rx digit (0+ (or digit "_")))) 49 | (oct-lit . ,(rx "0" (in "ocC") (in "0-7") (0+ (in "0-7_")))) 50 | (bin-lit . ,(rx "0" (in "bB") (in "01") (0+ (in "01_")))) 51 | 52 | (exponent 53 | . ,(rx (group (in "eE") (? (or "+" "-")) digit (0+ (or "_" digit))))) 54 | (open-paren . ,(rx (in "{[("))) 55 | (close-paren . ,(rx (in "}])"))) 56 | ;; FIXME: Use regexp-opt. 57 | (operator . ,(rx (or (1+ (in "-=+*/<>@$~&%|!?^.:\\")) 58 | (and 59 | symbol-start 60 | (or 61 | "and" "or" "not" "xor" "shl" 62 | "shr" "div" "mod" "in" "notin" "is" 63 | "isnot") 64 | symbol-end)))) 65 | (string-delimiter . ,(rx (and 66 | ;; Match even number of backslashes. 67 | (or (not (any ?\\ ?\")) point 68 | ;; Quotes might be preceded by a escaped quote. 69 | (and (or (not (any ?\\)) point) ?\\ 70 | (* ?\\ ?\\) (any ?\"))) 71 | (or (* ?\\) (* ?\\ ?\\)) 72 | ;; Match single or triple quotes of any kind. 73 | (group (or "\"" "\"\"\""))))) 74 | (string . ,(rx 75 | (minimal-match 76 | (group (syntax string-delimiter) 77 | (0+ (or any "\n")) 78 | (syntax string-delimiter))))) 79 | (character-delimiter 80 | ;; Implemented with 81 | ;; http://nim-lang.org/docs/manual.html#lexical-analysis-character-literals 82 | . ,(rx 83 | (group "'") 84 | (or 85 | ;; escaped characters 86 | (and ?\\ (or (in "abceflrtv\\\"'") 87 | (1+ digit) 88 | (and "x" (regex "[a-fA-F0-9]\\{2,2\\}")))) 89 | ;; One byte characters(except single quote and control characters) 90 | (eval (cons 'in (list (concat (char-to-string 32) "-" (char-to-string (1- ?\'))) 91 | (concat (char-to-string (1+ ?\')) "-" (char-to-string 126)))))) 92 | (group "'")))))) 93 | (append constituents1 constituents2)) 94 | ) ; end of eval-when-compile 95 | "Additional Nim specific sexps for `nim-rx'.") 96 | 97 | (eval-and-compile 98 | (defmacro nim-rx (&rest regexps) 99 | "Nim mode specialized rx macro. 100 | This variant of `rx' supports common nim named REGEXPS." 101 | (let ((rx-constituents (append nim-rx-constituents rx-constituents))) 102 | (cond ((null regexps) 103 | (error "No regexp")) 104 | ((cdr regexps) 105 | (rx-to-string (cons 'and regexps) t)) 106 | (t 107 | (rx-to-string (car regexps) t))))) 108 | 109 | (add-to-list 'nim-rx-constituents 110 | (cons 'identifier (nim-rx letter 111 | (* (or "_" alnum))))) 112 | 113 | (add-to-list 114 | 'nim-rx-constituents 115 | (cons 'quoted-chars 116 | (rx 117 | (and "`" 118 | (+? (char 119 | alnum "_^*[]!$%&+-./<=>?@|~")) 120 | "`")))) 121 | 122 | (add-to-list 'nim-rx-constituents 123 | (cons 'comment 124 | (rx (1+ (syntax comment-start)) 125 | (0+ (or (in " " word) nonl) (syntax comment-end))))) 126 | 127 | ;; Numbers 128 | (add-to-list 'nim-rx-constituents 129 | (cons 'int-lit 130 | (nim-rx (or hex-lit dec-lit oct-lit bin-lit)))) 131 | (add-to-list 'nim-rx-constituents 132 | (cons 'float-lit 133 | (nim-rx 134 | digit (0+ (or "_" digit)) 135 | (? (and "." (1+ (or "_" digit)))) 136 | (? exponent)))) 137 | 138 | (add-to-list 'nim-rx-constituents 139 | (cons 'float-suffix 140 | (nim-rx 141 | (group (or (and (in "fF") (or "32" "64" "128")) (in "dD")))))) 142 | 143 | (add-to-list 'nim-rx-constituents 144 | (cons 'nim-numbers 145 | (nim-rx 146 | symbol-start 147 | (or 148 | ;; float hex 149 | (group (group hex-lit) 150 | ;; "'" isn’t optional 151 | (group "'" float-suffix)) 152 | ;; float 153 | (group (group (or float-lit dec-lit oct-lit bin-lit)) 154 | (group (? "'") float-suffix)) 155 | ;; u?int 156 | (group 157 | (group int-lit) 158 | (? (group (? "'") 159 | (or (and (in "uUiI") (or "8" "16" "32" "64")) 160 | (in "uU")))))) 161 | symbol-end))) 162 | 163 | ;; pragma 164 | (add-to-list 165 | 'nim-rx-constituents 166 | (cons 'pragma 167 | (nim-rx 168 | (group "{." 169 | (minimal-match 170 | (1+ (or 171 | ;; any string 172 | ;; (emit pragma can include ".}") 173 | string 174 | any "\n"))) 175 | (? ".") "}")))) 176 | 177 | (add-to-list 'nim-rx-constituents 178 | (cons 'block-start (nim-rx (or decl-block block-start-defun)))) 179 | 180 | (add-to-list 'nim-rx-constituents 181 | (cons 'backquoted-chars 182 | (rx 183 | (syntax expression-prefix) 184 | (group-n 10 (minimal-match (1+ (not (syntax comment-end))))) 185 | (syntax expression-prefix)))) 186 | 187 | (add-to-list 'nim-rx-constituents 188 | (cons 'backticks 189 | (nim-rx 190 | (? (or line-start " " 191 | (syntax open-parenthesis) 192 | (syntax punctuation))) 193 | (group (or (group (syntax expression-prefix) 194 | backquoted-chars 195 | (syntax expression-prefix)) 196 | (group backquoted-chars))) 197 | (or " " 198 | line-end 199 | (syntax punctuation) 200 | (syntax comment-end) 201 | (syntax symbol) 202 | (syntax open-parenthesis) 203 | (syntax close-parenthesis))))) 204 | 205 | ) ; end of eval-and-compile 206 | 207 | (provide 'nim-rx) 208 | ;;; nim-rx.el ends here 209 | -------------------------------------------------------------------------------- /nim-suggest.el: -------------------------------------------------------------------------------- 1 | ;;; nim-suggest.el --- a plugin to use nimsuggest from Emacs -*- lexical-binding: t -*- 2 | 3 | ;; Description: A minor mode for Nim language tooling using nimsuggest 4 | ;; Author: Simon Hafner 5 | ;; Maintainer: Yuta Yamada 6 | 7 | ;; Package-Requires: ((emacs "24.4") (epc "0.1.1")) 8 | 9 | ;; This program is free software; you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation; either version 2, or (at your option) 12 | ;; any later version. 13 | ;; 14 | ;; This program is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | ;; 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with this program; see the file COPYING. If not, write to 21 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth 22 | ;; Floor, Boston, MA 02110-1301, USA. 23 | 24 | ;;; Commentary: 25 | 26 | ;; -- document is still work in progress -- 27 | ;; Supporting features: 28 | ;; - Xref package (cross reference package for Emacs) -- from Emacs 25.1 29 | ;; - flycheck/flymake (on the fly linter) 30 | ;; - flycheck-nimsugget is other repo 31 | ;; - flymake-nimsuggest is only available if you use Emacs 26 or higher 32 | ;; - eldoc (help on the hover) 33 | ;; - company-mode (auto-completion) 34 | 35 | ;; TODO: write manual configuration, but basically you can find 36 | ;; `nimsuggest-mode-hook' what this package does though. 37 | 38 | ;;; Code: 39 | 40 | (require 'nim-mode) 41 | (require 'epc) 42 | (require 'cl-lib) 43 | 44 | (defconst nim--epc-keywords 45 | ;; Those names come from suggest.nim 46 | '(:section ; sug, con, def, use, dus, highlight, outline 47 | :symkind ; symKind 48 | :qpath ; qualifiedPath 49 | :file ; filePath 50 | :forth ; type 51 | :line ; min is 1 in suggest.nim 52 | :column ; min is 0 in suggest.nim 53 | :doc ; document 54 | :quality ; rank of completion 55 | :prefix) ; matching state. See also prefixmatches.nim 56 | "Keywords for SexpNode type on nimsuggest.nim.") 57 | 58 | (defvar-local nimsuggest--state 'not-started) 59 | 60 | (cl-defstruct nim--epc 61 | section symkind qpath file forth line column doc quality prefix) 62 | 63 | (defun nimsuggest--parse-epc (epc-result method) 64 | "Parse EPC-RESULT according to METHOD." 65 | (cl-case method 66 | ((chk highlight outline) epc-result) 67 | ((sug con def use dus) 68 | (cl-mapcar 69 | (lambda (sublist) 70 | (apply #'make-nim--epc 71 | (cl-mapcan #'list nim--epc-keywords sublist))) 72 | epc-result)))) 73 | 74 | (defvar nimsuggest--epc-processes-alist nil) 75 | 76 | (defvar nimsuggest-get-option-function nil 77 | "Function to get options for nimsuggest.") 78 | 79 | ;; TODO: Is there something needed in this function? 80 | ;; https://irclogs.nim-lang.org/12-07-2017.html 81 | (defun nimsuggest-get-options (project-path) 82 | "Get prerequisite options for EPC mode. 83 | 84 | PROJECT-PATH is added as the last option." 85 | (delq nil 86 | (append nimsuggest-options nimsuggest-local-options 87 | ;; FIXME: 88 | ;; In recent nim’s update, this configuration no 89 | ;; longer can use. 90 | ;; (when (eq 'nimscript-mode major-mode) 91 | ;; '("--define:nimscript" "--define:nimconfig")) 92 | (list "--epc" project-path)))) 93 | 94 | (defun nimsuggest--get-epc-process (file) 95 | "Get active epc process instance for FILE." 96 | (let ((old-epc (cdr (assoc file nimsuggest--epc-processes-alist)))) 97 | (if (eq 'run (epc:manager-status-server-process old-epc)) 98 | (prog1 old-epc 99 | (nim-log "nimsuggest: use old EPC process\n - %s" old-epc)) 100 | (prog1 () (nimsuggest--kill-zombie-processes file))))) 101 | 102 | ;;;###autoload 103 | (defun nimsuggest-available-p () 104 | "Return non-nil if nimsuggest is available in current buffer." 105 | (and nimsuggest-path 106 | (not nim--inside-compiler-dir-p) 107 | ;; Prevent turn on nimsuggest related feature on org-src block 108 | ;; and nimscript-mode (nimsuggest doesn't support yet). 109 | ;; https://github.com/nim-lang/nimsuggest/issues/29 110 | (not (memq major-mode '(org-mode nimscript-mode))) 111 | (not (and (derived-mode-p 'org-mode) 112 | (fboundp 'org-in-src-block-p) 113 | (or (org-in-src-block-p) 114 | (org-in-src-block-p t)))))) 115 | 116 | (define-obsolete-function-alias 'nim-suggest-available-p 'nimsuggest-available-p "2017/9/02") 117 | 118 | (defun nimsuggest--safe-execute (file func) 119 | "Execute FUNC only if FILE buffer exists." 120 | (save-current-buffer 121 | (let ((buf (get-file-buffer file))) 122 | (when buf 123 | (unless (eq buf (current-buffer)) (set-buffer buf)) 124 | (funcall func))))) 125 | 126 | (defun nimsuggest--set-state (state file) 127 | "Set STATE for FILE's buffer." 128 | (nimsuggest--safe-execute 129 | file (lambda () (setq-local nimsuggest--state state)))) 130 | 131 | (defun nimsuggest--start-epc-deferred (file method callback) 132 | "Start EPC process for FILE." 133 | (deferred:nextc (nimsuggest--start-server-deferred nimsuggest-path file) 134 | (lambda (mngr) 135 | (push (cons file mngr) nimsuggest--epc-processes-alist) 136 | (nimsuggest--set-state 'ready file) 137 | (when (eq method 'chk) 138 | (nimsuggest--query method callback (nimsuggest--get-epc-process file)) 139 | ;; Reset `nimsuggest--state' if all epc processes for the file are dead. 140 | ;; Not sure if this is related to --refresh option. 141 | (catch 'exit 142 | (cl-loop for (f . _) in nimsuggest--epc-processes-alist 143 | if (equal file f) 144 | do (throw 'exit t) 145 | finally (nimsuggest--set-state 'not-started file))))))) 146 | 147 | (defun nimsuggest--start-server-deferred (server-prog file) 148 | "Copied from `epc:start-server-deferred' because original function uses `lexicall-let'. 149 | It unable to use from this nim-suggest.el due to some error. (void-variable self or something) 150 | Almost structure is same, but below two values should be changed depending on 151 | nimsuggest's loading time: 152 | - `nimsuggest-accept-process-delay' 153 | - `nimsuggest-accept-process-timeout-count'" 154 | ;; TODO: let EPC author knows the issue 155 | (let* ((server-args (nimsuggest-get-options file)) 156 | (uid (epc:uid)) 157 | (process-name (epc:server-process-name uid)) 158 | (process-buffer (get-buffer-create (epc:server-buffer-name uid))) 159 | (process (apply 'start-process 160 | process-name process-buffer 161 | server-prog server-args)) 162 | (mngr (make-epc:manager 163 | :server-process process 164 | :commands (cons server-prog server-args) 165 | :title (mapconcat 'identity (cons server-prog server-args) " "))) 166 | (cont 1) port) 167 | (set-process-query-on-exit-flag process nil) 168 | (deferred:$ 169 | (deferred:next 170 | ;; self recursion during `deferred:lambda' til it gets port 171 | ;; number or emits timeout error. 172 | (deferred:lambda (_) 173 | (accept-process-output process 0 nil t) 174 | (let ((port-str (with-current-buffer process-buffer 175 | (buffer-string)))) 176 | (cond 177 | ((string-match "^[0-9]+$" port-str) 178 | (setq port (string-to-number port-str) 179 | cont nil)) 180 | ((< 0 (length port-str)) 181 | (error "Server may raise an error \ 182 | Use \"M-x epc:pop-to-last-server-process-buffer RET\" \ 183 | to see full traceback:\n%s" port-str)) 184 | ((not (eq 'run (process-status process))) 185 | (setq cont nil)) 186 | (t 187 | (cl-incf cont) 188 | (when (< nimsuggest-accept-process-timeout-count cont) 189 | (nimsuggest--set-state 'no-response file) 190 | ;; timeout 15 seconds (100 * 150) 191 | (error "Timeout server response")) 192 | (deferred:nextc (deferred:wait nimsuggest-accept-process-delay) 193 | self)))))) 194 | (deferred:nextc it 195 | (lambda (_) 196 | (setf (epc:manager-port mngr) port) 197 | (setf (epc:manager-connection mngr) (epc:connect "localhost" port)) 198 | mngr)) 199 | (deferred:nextc it 200 | (lambda (mngr) (epc:init-epc-layer mngr)))))) 201 | 202 | (defun nimsuggest--query (method callback epc-process) 203 | "Query to nimsuggest of EPC-PROCESS with METHOD. 204 | CALLBACK function will be applied when nimsuggest returns the result." 205 | (when (and (nimsuggest-available-p) epc-process) 206 | ;; See also compiler/modulegraphs.nim for dirty file 207 | (let ((temp-dirty-file (nimsuggest--save-buffer-temporarly)) 208 | (buf (current-buffer))) 209 | (deferred:$ 210 | (epc:call-deferred 211 | epc-process 212 | (prog1 method 213 | (nim-log "EPC-1 %S" (symbol-name method))) 214 | (cl-case method 215 | ((chk highlight outline) 216 | (list (buffer-file-name) 217 | -1 -1 218 | temp-dirty-file)) 219 | (t 220 | (list (buffer-file-name) 221 | (line-number-at-pos) 222 | (current-column) 223 | temp-dirty-file)))) 224 | (deferred:nextc it 225 | (lambda (x) 226 | (nim-log "EPC(%S) nextc" (symbol-name method)) 227 | (when x (funcall callback (nimsuggest--parse-epc x method))))) 228 | (deferred:watch it 229 | (lambda (_) 230 | (unless (get-buffer buf) 231 | (nim-log "EPC(%S) delete %s" (symbol-name method) buf) 232 | (delete-file temp-dirty-file)))) 233 | (deferred:error it 234 | (lambda (err) 235 | ;; Note that it seems like this error clause is not *rare* to 236 | ;; called when you write broken nim code. (probably with 237 | ;; template or macro) So, just put a log function and see 238 | ;; what's going on only if users or I are interested. 239 | (nim-log "EPC(%S) ERROR %s" 240 | (symbol-name method) (error-message-string err)))))))) 241 | 242 | (defun nimsuggest--call-epc (method callback) 243 | "Call the nimsuggest process on point. 244 | 245 | Call the nimsuggest process responsible for the current buffer. 246 | All commands work with the current cursor position. METHOD can be 247 | one of: 248 | 249 | sug: suggest a symbol 250 | con: suggest, but called at fun(_ <- 251 | def: where the symbol is defined 252 | use: where the symbol is used 253 | dus: def + use 254 | 255 | The CALLBACK is called with a list of ‘nim--epc’ structs." 256 | (let ((file (buffer-file-name))) 257 | (cl-case nimsuggest--state 258 | ((never connecting) nil) ; do nothing 259 | (no-response 260 | (nimsuggest--set-state 'never file) 261 | ;; Maybe M-x `epc:pop-to-last-server-process-buffer' would be 262 | ;; helpful to check the cause. 263 | (message "nimsuggest-mode reached timeout (about %dsec) due to no response from server. 264 | This feature will be blocked on this %s." 265 | (/ (* nimsuggest-accept-process-delay 266 | nimsuggest-accept-process-timeout-count) 267 | 1000) 268 | file)) 269 | (ready 270 | (nimsuggest--query method callback (nimsuggest--get-epc-process file)) 271 | ;; Reset `nimsuggest--state' if all epc processes for the file are dead. 272 | ;; Not sure if this is related to --refresh option. 273 | (catch 'exit 274 | (cl-loop for (f . _) in nimsuggest--epc-processes-alist 275 | if (equal file f) 276 | do (throw 'exit t) 277 | finally (nimsuggest--set-state 'not-started file)))) 278 | (not-started 279 | (setq-local nimsuggest--state 'connecting) 280 | (deferred:$ 281 | (deferred:next 282 | (nimsuggest--start-epc-deferred file method callback)) 283 | (deferred:error it 284 | (lambda (err) (nim-log "EPC(startup) ERROR %s" (error-message-string err)))))) 285 | (t 286 | (error (format "This shouldn't happen: nimsuggest--state is %s" nimsuggest--state)))))) 287 | 288 | (defun nimsuggest--call-sync (method callback) 289 | "Synchronous call for nimsuggest using METHOD. 290 | The CALLBACK function is called when it got the response." 291 | (let* ((buf (current-buffer)) 292 | (start (time-to-seconds)) 293 | (res 'trash)) 294 | (nimsuggest--call-epc 295 | method 296 | (lambda (candidates) 297 | (when (eq (current-buffer) buf) 298 | (setq res (funcall callback candidates))))) 299 | (while (and (eq 'trash res) (eq (current-buffer) buf)) 300 | (if (> (- (time-to-seconds) start) 2) 301 | (nim-log "EPC-sync(%s): timeout %d sec" (symbol-name method) 2) 302 | (sleep-for 0.03))) 303 | (unless (eq 'trash res) 304 | res))) 305 | 306 | (defun nimsuggest--get-dirty-dir () 307 | "Return temp directory. 308 | The directory name consists of `nimsuggest-dirty-directory' and current 309 | frame number. The frame number is required to prevent Emacs 310 | crash when some emacsclients open the same file." 311 | (let* ((frame-num (car (last (split-string (format "%s" (selected-frame)) " ")))) 312 | (frame-num-str (substring frame-num 0 (1- (length frame-num))))) 313 | (file-name-as-directory (concat nimsuggest-dirty-directory frame-num-str)))) 314 | 315 | (defun nimsuggest--get-temp-file-name () 316 | "Get temp file name." 317 | (mapconcat 'directory-file-name 318 | `(,(nimsuggest--get-dirty-dir) 319 | ,(cl-case system-type 320 | ((ms-dos windows-nt cygwin) 321 | ;; For bug #119, convert ":" to "꞉" (U+A789) 322 | (concat "/" 323 | (replace-regexp-in-string 324 | ":" (char-to-string #xA789) 325 | buffer-file-name))) 326 | (t ; for *nix system 327 | buffer-file-name))) 328 | "")) 329 | 330 | (defun nimsuggest--make-tempdir (tempfile) 331 | "Make temporary directory for TEMPFILE." 332 | (let* ((tempdir (file-name-directory tempfile))) 333 | (unless (file-exists-p tempdir) 334 | (make-directory tempdir t)))) 335 | 336 | (defun nimsuggest--save-buffer-temporarly () 337 | "Save the current buffer and return the location." 338 | (let* ((temporary-file-directory nimsuggest-dirty-directory) 339 | (filename (nimsuggest--get-temp-file-name))) 340 | (nimsuggest--make-tempdir filename) 341 | (save-restriction 342 | (widen) 343 | (write-region (point-min) (point-max) filename nil 1)) 344 | filename)) 345 | 346 | (add-hook 'kill-emacs-hook 'nimsugget--delete-temp-directory) 347 | (defun nimsugget--delete-temp-directory () 348 | "Delete temporary files directory for nimsuggest." 349 | (when (file-exists-p nimsuggest-dirty-directory) 350 | (delete-directory (file-name-directory nimsuggest-dirty-directory) t))) 351 | 352 | (defun nimsuggest--kill-zombie-processes (&optional ppath) 353 | "Kill needless zombie processes, which correspond to PPATH." 354 | (setq nimsuggest--epc-processes-alist 355 | (cl-loop for (file . manager) in nimsuggest--epc-processes-alist 356 | if (and (epc:live-p manager) 357 | (or (and ppath (equal ppath file)) 358 | (not ppath))) 359 | collect (cons file manager) 360 | else do (epc:stop-epc manager)))) 361 | 362 | (defvar nimsuggest-mode-map 363 | (let ((map (make-sparse-keymap))) 364 | (define-key map (kbd "C-c C-d") #'nimsuggest-show-doc) 365 | map)) 366 | 367 | (defcustom nimsuggest-mode-hook nil 368 | "Hook run when entering Nimsuggest mode." 369 | :options '(flycheck-nimsuggest-setup nimsuggest-flymake-setup nimsuggest-xref-setup) 370 | :type 'hook 371 | :group 'nim) 372 | 373 | ;;;###autoload 374 | (define-minor-mode nimsuggest-mode 375 | "Minor mode for nimsuggest." 376 | :lighter " nimsuggest" 377 | :keymap nimsuggest-mode-map 378 | (when nimsuggest-mode 379 | (nimsuggest-ensure))) 380 | 381 | (defun nimsuggest-force-stop () 382 | "Try to stop nimsuggest related things, but not well tested." 383 | (interactive) 384 | (remove-hook 'flycheck-checkers 'nim-nimsuggest) 385 | (remove-hook 'flymake-diagnostic-functions 'flymake-nimsuggest t) 386 | (nim-eldoc-off) 387 | (nimsuggest-xref-on-or-off 'off)) 388 | 389 | (defun nimsuggest-ensure () 390 | "Ensure that users installed nimsuggest executable." 391 | ;; I've seen so many people just stacked to install nimsuggest at 392 | ;; first time. Probably this package's name is kinda confusing. 393 | (interactive) 394 | (let ((msg "Nimsuggest-mode needs external tool called nimsuggest. 395 | Generally you can build by './koch tools' or '.koch nimsuggest' 396 | on Nim repo (check koch.nim file), but it's good to check README 397 | on Nim's official repository on yourself in case this document 398 | was outdated.")) 399 | (when (or (bound-and-true-p eldoc-mode) 400 | (bound-and-true-p global-eldoc-mode)) 401 | (setq nimsuggest-eldoc-function 'nimsuggest-eldoc--nimsuggest)) 402 | (when (not nimsuggest-path) 403 | (nimsuggest-force-stop) 404 | (error msg)) 405 | (when (not (file-executable-p nimsuggest-path)) 406 | (nimsuggest-force-stop) 407 | (error "`nimsuggest-path' isn't executable; %s" msg)) 408 | (if nimsuggest-mode 409 | (nim-log "nimsuggest-mode started") 410 | (nim-log "nimsuggest-mode stopped")))) 411 | 412 | 413 | ;; Utilities 414 | 415 | (defun nimsuggest--put-face (text face) 416 | "Put FACE on the TEXT." 417 | (when (and text (string< "" text)) 418 | (add-text-properties 419 | 0 (length text) 420 | `(face ,face) 421 | text))) 422 | 423 | (defun nimsuggest--parse (forth) 424 | "Parse FORTH element." 425 | (when (string-match 426 | (rx (group (1+ word)) (0+ " ") 427 | (group (1+ nonl))) 428 | forth) 429 | (let ((first (match-string 1 forth)) 430 | (other (match-string 2 forth))) 431 | (cons first other)))) 432 | 433 | (defun nimsuggest--trim (str) 434 | "Adjust STR for mini buffer." 435 | (let ((max-width (- (frame-width) 4))) ; <- just for buffer, probably 436 | (if (< (length str) max-width) ; it depends on terminal or GUI Emacs 437 | str 438 | (let* ((short-str (substring str 0 (- (frame-width) 4))) 439 | (minus-offset 440 | (cl-loop with num = 0 441 | for s in (delq "" (split-string (reverse short-str) "")) 442 | if (equal s ".") do (cl-return num) 443 | else do (cl-incf num) 444 | finally return 0))) 445 | (substring short-str 0 (- (length short-str) minus-offset)))))) 446 | 447 | (defun nimsuggest--format (forth symKind qpath doc) 448 | "Highlight returned result from nimsuggest of FORTH, SYMKIND, QPATH, and DOC." 449 | (let* ((doc (mapconcat 'identity (split-string doc "\n") " ")) 450 | (name 451 | (if (eq (length (cdr qpath)) 1) 452 | (cadr qpath) 453 | (mapconcat 'identity (cdr qpath) ".")))) 454 | (nimsuggest--put-face doc font-lock-doc-face) 455 | (pcase (list symKind) 456 | (`(,(or "skProc" "skField" "skTemplate" "skMacro")) 457 | (when (string< "" forth) 458 | (cl-destructuring-bind (ptype . typeinfo) (nimsuggest--parse forth) 459 | (when (equal "proc" ptype) 460 | (nimsuggest--put-face name font-lock-function-name-face) 461 | (let* ((func (format "%s %s" name typeinfo))) 462 | (nimsuggest--trim 463 | (if (string= "" doc) 464 | (format "%s" func) 465 | (format "%s %s" func doc)))))))) 466 | (`(,(or "skVar" "skLet" "skConst" "skResult" "skParam")) 467 | (let ((sym (downcase (substring symKind 2 (length symKind))))) 468 | (nimsuggest--put-face sym font-lock-keyword-face) 469 | (nimsuggest--put-face name 470 | (cond ((member symKind '("skVar" "skResult")) 471 | '(face font-lock-variable-name-face)) 472 | ((member symKind '("skLet" "skConst")) 473 | '(face font-lock-constant-face)) 474 | (t '(face font-lock-keyword-face)))) 475 | (nimsuggest--trim 476 | (format "%s %s : %s" sym name 477 | (cond 478 | ((string< "" forth) forth) 479 | (t "no doc")))))) 480 | (`("skType") 481 | (nimsuggest--put-face name font-lock-type-face) 482 | (nimsuggest--trim 483 | (if (not (string< "" doc)) 484 | (format "%s: no doc" name) 485 | (format "%s: %s" name doc))))))) 486 | 487 | 488 | ;;; misc 489 | 490 | ;; work in progress 491 | 492 | (defcustom nimsuggest-doc-directive 493 | 'def 494 | "Directive passed by nimsuggest for `nimsuggest-show-doc'." 495 | :type '(choice 496 | (const :tag "suggest" 'sug) 497 | (const :tag "definition" 'def)) 498 | :group 'nim) 499 | 500 | (defvar nimsuggest--doc-args nil 501 | "Internal variable to store document data.") 502 | 503 | (defun nimsuggest-show-doc () 504 | "Show document in dedicated *nim-doc* buffer." 505 | (interactive) 506 | (nimsuggest--call-epc 507 | nimsuggest-doc-directive 508 | (lambda (args) 509 | (if (and (not args) (not (eq 'sug nimsuggest-doc-directive))) 510 | ;; Fallback if there is no result from nimsuggest by 'sug 511 | (let ((nimsuggest-doc-directive 'sug)) 512 | (nimsuggest-show-doc)) 513 | ;; TODO: should I filter returned result by current position's identifier? 514 | (setq nimsuggest--doc-args (cl-loop for i from 0 to (1- (length args)) 515 | collect (cons (1+ i) (nth i args)))) 516 | (nimsuggest--show-doc))))) 517 | 518 | (defun nimsuggest--format-doc-org (doc) 519 | (mapc 520 | (lambda (x) (setq doc (replace-regexp-in-string (car x) (nth 1 x) doc))) 521 | `((,(concat 522 | ".+::.*\n" 523 | "\\(\\(" 524 | "\\([ ]\\{1,\\}.+\\)?\n?" 525 | "\\)*\\)") 526 | "#+BEGIN_SRC nim\n\\1\n#+END_SRC") ; turn code blocks into org babel 527 | ("\n*\\(\n#\\+BEGIN_SRC nim\n\\)\n*" "\n\\1") ; cleanup extra newlines 528 | ("\n*\\(\n#\\+END_SRC\\)\n*" "\\1\n\n") ; cleanup extra newlines 529 | ("`\\([^`]+\\)`:idx:" "\\1") ; clean :idx: fields 530 | ("``" "~") ; inline code highlighting 531 | ("`\\([A-z0-9\\-]+\\) *`" "~\\1~") ; inline code highlighting 532 | ("`\\([^<]+[^\n ]\\)[ \n]*<[^ \n]+>`_" "~\\1~") ; turn doc links into inline code 533 | ("^\\* " "- ") ; * bullets into - 534 | ("\\*\\*\\([^*]+\\)\\*\\*" "*\\1*") ; bold 535 | ("^\\(.+\\)\n[=]+$" "** \\1") ; format headers 536 | ("^\\(.+\\)\n[-]+$" "*** \\1") 537 | ("^\\(.+\\)\n[~]+$" "**** \\1") 538 | ("^\\(~.+~\\)\\([ ]\\{2,\\}\\)" "\\1 \\2"))) ; add missing spaces to tables 539 | doc) 540 | 541 | (defun nimsuggest--link-location-org (location) 542 | (replace-regexp-in-string ".+" "[[file:\\&][\\&]]" location)) 543 | 544 | (defun nimsuggest--doc-insert-nav (def) 545 | (let ((nominator (caar nimsuggest--doc-args)) 546 | (denominator (length nimsuggest--doc-args))) 547 | (format "%s %s\n" 548 | (mapconcat 'identity (nim--epc-qpath def) " ") 549 | (if (eq 1 denominator) 550 | "" 551 | (format "%s/%s %s" nominator denominator 552 | "-- < next, > previous"))))) 553 | 554 | (defun nimsuggest--header-rst(header) 555 | (format "%s\n%s" header (make-string (length header) ?#))) 556 | 557 | (defun nimsuggest--show-doc-rst (def) 558 | "Display Nim docs using rst-mode." 559 | (cl-mapcar 560 | (lambda (x) (insert (concat x "\n"))) 561 | (list 562 | ;; (format "debug %s\n" nimsuggest--doc-args) 563 | (nimsuggest--doc-insert-nav def) 564 | (format "%s\n%s %s\n" 565 | (nimsuggest--header-rst "Signature") 566 | (nim--epc-symkind def) 567 | (nim--epc-forth def)) 568 | (unless (string= "" (nim--epc-doc def)) 569 | (format "%s\n%s\n" 570 | (nimsuggest--header-rst "Documentation") 571 | (nim--epc-doc def))) 572 | (format "%s\n%s\n" 573 | (nimsuggest--header-rst "Location") 574 | (nim--epc-file def)))) 575 | (when (fboundp 'rst-mode) 576 | (rst-mode))) 577 | 578 | (defun nimsuggest--show-doc-org (def) 579 | "Display Nim docs using org-mode formatting." 580 | (cl-mapcar 581 | (lambda (x) (insert (concat x "\n"))) 582 | (list 583 | ;; (format "debug %s\n" nimsuggest--doc-args) 584 | (nimsuggest--doc-insert-nav def) 585 | (format "* Signature\n%s %s\n" 586 | (nim--epc-symkind def) 587 | (nim--epc-forth def)) 588 | (unless (string= "" (nim--epc-doc def)) 589 | (format "* Documentation\n%s\n" 590 | (nimsuggest--format-doc-org (nim--epc-doc def)))) 591 | (format "* Location\n%s\n" 592 | (nimsuggest--link-location-org (nim--epc-file def))))) 593 | (when (fboundp 'org-mode) 594 | (org-mode) 595 | (org-show-all))) 596 | 597 | (defun nimsuggest--show-doc () 598 | "Internal function for `nimsuggest-show-doc'." 599 | (let ((def (cdar nimsuggest--doc-args))) 600 | (get-buffer-create "*nim-doc*") 601 | (unless (equal (current-buffer) (get-buffer "*nim-doc*")) 602 | (switch-to-buffer-other-window "*nim-doc*")) 603 | (setq buffer-read-only nil) 604 | (erase-buffer) 605 | (funcall nimsuggest-show-doc-function def) 606 | (when (fboundp 'evil-mode) 607 | (evil-make-intercept-map nimsuggest-doc-mode-map)) 608 | (use-local-map nimsuggest-doc-mode-map) 609 | (goto-char (point-min)) 610 | (setq buffer-read-only t))) 611 | 612 | (defun nimsuggest-doc-next () 613 | "Move to next page." 614 | (interactive) 615 | (if (not (< 0 (length nimsuggest--doc-args))) 616 | (minibuffer-message "there is no next") 617 | (let ((popped (pop nimsuggest--doc-args))) 618 | (setq nimsuggest--doc-args (append nimsuggest--doc-args (list popped))) 619 | (nimsuggest--show-doc)))) 620 | 621 | (defun nimsuggest-doc-previous () 622 | "Move to previous page." 623 | (interactive) 624 | (if (not (< 0 (length nimsuggest--doc-args))) 625 | (minibuffer-message "there is no previous") 626 | (let* ((rargs (reverse nimsuggest--doc-args)) 627 | (popped (pop rargs))) 628 | (setq rargs (append rargs (list popped)) 629 | nimsuggest--doc-args (reverse rargs)) 630 | (nimsuggest--show-doc)))) 631 | 632 | 633 | ;;; Flymake integration 634 | 635 | ;; From Emacs 26, flymake was re-written by João Távora. 636 | ;; It supports asynchronous backend, so enable it if users 637 | ;; turned on the flymake-mode. 638 | 639 | ;; Manual configuration: 640 | ;; (add-hook 'nimsuggest-mode-hook 'nimsuggest-flymake-setup) 641 | 642 | ;; TODO: specify more specific version 643 | (when (version<= "26" (number-to-string emacs-major-version)) 644 | (add-hook 'nimsuggest-mode-hook 'nimsuggest-flymake-setup)) 645 | 646 | ;;;###autoload 647 | (defun nimsuggest-flymake-setup() 648 | "Kinda experimental function to use flymake on Emacs 26." 649 | (when (and (bound-and-true-p flymake-mode) 650 | (not (bound-and-true-p flycheck-mode))) 651 | (if nimsuggest-mode 652 | (add-hook 'flymake-diagnostic-functions 'flymake-nimsuggest nil t) 653 | (remove-hook 'flymake-diagnostic-functions 'flymake-nimsuggest t)))) 654 | 655 | (defun nimsuggest--flymake-filter (errors buffer) 656 | "Remove not related ERRORS in the BUFFER." 657 | (cl-loop for (_ _ _ file type line col text _) in errors 658 | if (and (eq buffer (get-file-buffer file)) 659 | (<= 1 line) (<= 0 col)) 660 | ;; column needs to be increased by 1 to highlight correctly 661 | collect (list file type line (1+ col) text))) 662 | 663 | (defun nimsuggest--flymake-region (file buf line col) 664 | "Calculate beg and end for FILE, BUF, LINE, and COL. 665 | Workaround for https://github.com/nim-lang/nim-mode/issues/183." 666 | (if (not (eq (get-file-buffer file) (current-buffer))) 667 | (cons 0 1) 668 | (cl-letf* (((symbol-function 'end-of-thing) 669 | (lambda (&rest _r) nil))) 670 | (funcall 'flymake-diag-region buf line col)))) 671 | 672 | (defun nimsuggest--flymake-error-parser (errors buffer) 673 | "Return list of result of `flymake-make-diagnostic' from ERRORS. 674 | The list can be nil. ERRORS will be skipped if BUFFER and 675 | parsed file was different." 676 | (cl-loop with errs = (nimsuggest--flymake-filter errors buffer) 677 | for (file typ line col text) in errs 678 | for type = (cl-case (string-to-char typ) 679 | (?E :error) 680 | (?W :warning) 681 | (t :note)) 682 | for (beg . end) = (nimsuggest--flymake-region file buffer line col) 683 | collect (funcall 'flymake-make-diagnostic buffer beg end type text))) 684 | 685 | (defun flymake-nimsuggest (report-fn &rest _args) 686 | "A Flymake backend for Nim language using Nimsuggest. 687 | See `flymake-diagnostic-functions' for REPORT-FN and ARGS." 688 | (let ((buffer (current-buffer))) 689 | (nimsuggest--call-epc 690 | 'chk 691 | (lambda (errors) 692 | (nim-log "FLYMAKE(start): report(s) number of %i" (length errors)) 693 | (condition-case err 694 | (let ((report-action 695 | (nimsuggest--flymake-error-parser errors buffer))) 696 | (funcall report-fn report-action)) 697 | (error 698 | (nim-log "FLYMAKE(error): %s" (error-message-string err)))))))) 699 | 700 | ;; TODO: is this really needed? 701 | (defun nimsuggest-flymake--panic (report-fn err) 702 | "TODO: not sure where to use this yet... 703 | Using this function cause to stop flymake completely which is not 704 | suitable for nimsuggest because nimsuggest re-start after its 705 | crush. 706 | 707 | You can find explanation REPORT-FN at `flymake-diagnostic-functions' 708 | and the ERR is captured error." 709 | (when (member 'flymake-nimsuggest flymake-diagnostic-functions) 710 | (nim-log-err "FLYMAKE(ERR): %s" err) 711 | (funcall report-fn :panic :explanation err))) 712 | 713 | 714 | ;;; ElDoc for nimsuggest 715 | 716 | (defvar nimsuggest-eldoc--data nil) 717 | 718 | ;;;###autoload 719 | (defun nimsuggest-eldoc--nimsuggest () 720 | "Update `eldoc-last-message' by nimsuggest's information." 721 | (when (nimsuggest-available-p) 722 | (unless (nimsuggest-eldoc--same-try-p) 723 | (nimsuggest-eldoc--call)) 724 | (when (eq (line-number-at-pos) 725 | (assoc-default :line nimsuggest-eldoc--data)) 726 | (assoc-default :str nimsuggest-eldoc--data)))) 727 | 728 | (defun nimsuggest-eldoc--same-try-p () 729 | "Predicate function if same try or not." 730 | (or (and (equal (nim-current-symbol) 731 | (assoc-default :name nimsuggest-eldoc--data)) 732 | (eq (assoc-default :line nimsuggest-eldoc--data) 733 | (line-number-at-pos))) 734 | (and (nim-eldoc-inside-paren-p) 735 | (save-excursion 736 | (nimsuggest-eldoc--move) 737 | (or 738 | ;; for template 739 | (eq (point) (assoc-default :pos nimsuggest-eldoc--data)) 740 | ;; for proc 741 | (eq (1- (point)) (assoc-default :pos nimsuggest-eldoc--data))))))) 742 | 743 | (defun nimsuggest-eldoc--move () 744 | "Move cursor appropriate point where calling nimsuggest is suitable." 745 | (let ((pos (point)) 746 | (ppss (syntax-ppss))) 747 | (when (nim-eldoc-inside-paren-p) 748 | (save-excursion 749 | (goto-char (nth 1 ppss)) 750 | (when (looking-back nim-eldoc--skip-regex nil) 751 | (goto-char pos)))))) 752 | 753 | (defun nim-eldoc-format-string (defs) 754 | "Format data inside DEFS for eldoc. 755 | DEFS is group of definitions from nimsuggest." 756 | ;; TODO: switch if there are multiple defs 757 | (nim-log "ELDOC format") 758 | (let ((data (cl-first defs))) 759 | (apply 'nimsuggest--format 760 | (mapcar (lambda (x) (funcall x data)) 761 | '(nim--epc-forth nim--epc-symkind nim--epc-qpath nim--epc-doc))))) 762 | 763 | (defun nimsuggest-eldoc--call () 764 | "Call nimsuggest for eldoc." 765 | (save-excursion 766 | (nimsuggest-eldoc--move) 767 | (nim-log "ELDOC-1") 768 | (nimsuggest--call-epc 'dus 'nimsuggest-eldoc--update))) 769 | 770 | (defun nimsuggest-eldoc--update (def-use) 771 | "Update eldoc information from DEF-USE of nimsuggest." 772 | (if (not (nim-eldoc--try-p)) 773 | (nim-log "ELDOC stop update") 774 | (nim-log "ELDOC update") 775 | (if def-use 776 | (nimsuggest-eldoc--update-1 def-use) 777 | (save-excursion 778 | (when (nim-eldoc-inside-paren-p) 779 | (nimsuggest-eldoc--move) 780 | (backward-char) 781 | (nimsuggest--call-epc 'dus 'nimsuggest-eldoc--update-1)))))) 782 | 783 | (defun nimsuggest-eldoc--update-1 (epc-result) 784 | "Save EPC-RESULT into `nimsuggest-eldoc--data'. 785 | And show message of `eldoc-last-message'. 786 | The EPC-RESULT can be result of both def and/or dus." 787 | (when epc-result 788 | (setq nimsuggest-eldoc--data 789 | (list 790 | (cons :str (nim-eldoc-format-string epc-result)) 791 | (cons :line (line-number-at-pos)) 792 | (cons :name (nim-current-symbol)) 793 | (cons :pos (point)))) 794 | (setq eldoc-last-message (assoc-default :str nimsuggest-eldoc--data)) 795 | (message eldoc-last-message))) 796 | 797 | 798 | ;;; xref integration 799 | ;; This package likely be supported on Emacs 25.1 or later 800 | 801 | (defvar nimsuggest-find-definition-function nil 802 | "Function for `nimsuggest-find-definition'.") 803 | 804 | ;;;###autoload 805 | (add-hook 'nimsuggest-mode-hook 'nimsuggest-xref-setup) 806 | ;;;###autoload 807 | (defun nimsuggest-xref-setup () 808 | "Setup xref backend for nimsuggest." 809 | (cond 810 | ((not (nimsuggest-available-p)) 811 | (nim-log "xref package needs nimsuggest")) 812 | ((not (require 'xref nil t)) 813 | (setq nimsuggest-find-definition-function 'nimsuggest-find-definition-old) 814 | ;; Note below configuration were removed on the future 815 | (define-key nimsuggest-mode-map (kbd "M-.") #'nimsuggest-find-definition) 816 | (define-key nimsuggest-mode-map (kbd "M-,") #'pop-tag-mark)) 817 | ((version<= "25.1.0" emacs-version) 818 | (require 'xref) 819 | (setq nimsuggest-find-definition-function 'xref-find-definitions) 820 | (nimsuggest-xref-on-or-off (if nimsuggest-mode 'on 'off))) 821 | (t (nim-log "xref unexpected condition")))) 822 | 823 | (defun nimsuggest-xref-on-or-off (on-or-off) 824 | "Turn on or off xref feature for nimsuggest backend. 825 | You can specify `on' or `off' symbol as the ON-OR-OFF." 826 | (cl-case on-or-off 827 | (on (add-hook 'xref-backend-functions #'nimsuggest--xref-backend nil t)) 828 | (off (remove-hook 'xref-backend-functions #'nimsuggest--xref-backend t)))) 829 | 830 | (defun nimsuggest-find-definition () 831 | "This function is preserved for backward compatibility. 832 | If your Emacs support cross reference library `xref' (from Emacs 833 | 25.1), you might want to use `xref-find-definition' instead which 834 | binds to `M-.' in default." 835 | (interactive) 836 | (call-interactively nimsuggest-find-definition-function)) 837 | 838 | ;; Define xref backend for nimsuggest 839 | (with-eval-after-load "xref" 840 | (defun nimsuggest--xref-backend () 'nimsuggest) 841 | (cl-defmethod xref-backend-identifier-at-point ((_backend (eql nimsuggest))) 842 | "Return string or nil for identifier at point." 843 | ;; Well this function may not needed for current xref functions for 844 | ;; nimsuggest backend. 845 | (with-syntax-table nim-dotty-syntax-table 846 | (let ((thing (thing-at-point 'symbol))) 847 | (and thing (substring-no-properties thing))))) 848 | 849 | (defun nimsuggest--xref-make-obj (id def) 850 | (let ((summary id) 851 | (location (xref-make-file-location 852 | (nim--epc-file def) 853 | (nim--epc-line def) 854 | (nim--epc-column def)))) 855 | (xref-make summary location))) 856 | 857 | (defun nimsuggest--xref (query id) 858 | (nimsuggest--call-sync 859 | query 860 | (lambda (results) 861 | (cond 862 | ((null results) nil) 863 | ((listp results) 864 | (cl-loop for result in results 865 | collect (nimsuggest--xref-make-obj id result))))))) 866 | 867 | (cl-defmethod xref-backend-definitions ((_backend (eql nimsuggest)) id) 868 | (nimsuggest--xref 'def id)) 869 | 870 | (cl-defmethod xref-backend-references ((_backend (eql nimsuggest)) id) 871 | (nimsuggest--xref 'dus id)) 872 | 873 | ;; just define empty backend to use `xref-backend-references' for 874 | ;; nimsuggest. 875 | (cl-defmethod xref-backend-identifier-completion-table 876 | ((_backend (eql nimsuggest)))) 877 | 878 | ;; Not implement yet, or not sure maybe, won't... 879 | ;; (cl-defmethod xref-backend-apropos ((_backend (eql nimsuggest)) pattern)) 880 | 881 | ) ; end of with-eval-after-load xref 882 | 883 | ;; Workaround for old Emacsen 884 | ;; TODO: remove those stuff after Emacs 25 or 26 is dominant. 885 | (require 'etags) 886 | (defun nimsuggest-find-definition-old () 887 | "Go to the definition of the symbol currently under the cursor." 888 | (nimsuggest--call-epc 889 | 'def 890 | (lambda (defs) 891 | (let ((def (cl-first defs))) 892 | (when (not def) (error "Definition not found")) 893 | (if (fboundp 'xref-push-marker-stack) 894 | (xref-push-marker-stack) 895 | (with-no-warnings 896 | (ring-insert find-tag-marker-ring (point-marker)))) 897 | (find-file (nim--epc-file def)) 898 | (goto-char (point-min)) 899 | (forward-line (1- (nim--epc-line def))))))) 900 | 901 | (define-obsolete-function-alias 'nim-goto-sym 'nimsuggest-find-definition 902 | "2017/9/02") 903 | 904 | 905 | ;;; Debug 906 | (defun nimsuggest--put (text where) 907 | "Put text property to TEXT of WHERE." 908 | (put-text-property where (1+ where) 'face 'success text)) 909 | 910 | (defun nimsuggest--debug-prompt () 911 | "Return string for read key." 912 | (let ((msgs '(sug (def . 2) dus use con highlight outline))) 913 | (cl-loop for msg in msgs 914 | for text = (symbol-name (if (symbolp msg) msg (car msg))) 915 | for where = (if (symbolp msg) 0 (cdr msg)) 916 | do (nimsuggest--put text where) 917 | collect text into res 918 | finally return (mapconcat 'identity res " ")))) 919 | 920 | (defun nimsuggest--debug-print () 921 | "Print result of nimsuggest's epc call." 922 | (interactive) 923 | (let ((input (read-key (nimsuggest--debug-prompt)))) 924 | (let ((key (cl-case input 925 | (?s 'sug) 926 | (?f 'def) 927 | (?d 'dus) 928 | (?u 'use) 929 | (?c 'con) 930 | (?h 'highlight) 931 | (?o 'outline)))) 932 | (if (null key) 933 | (minibuffer-message "unexpected key %c" input) 934 | (message "calling nimsuggest ...") 935 | (nimsuggest--call-epc 936 | key 937 | (lambda (args) 938 | (message "nimsuggest's result:\n%s" args))))))) 939 | 940 | ;; (define-key nimsuggest-mode-map (kbd "C-8") 'nimsuggest--debug-print) 941 | 942 | (provide 'nim-suggest) 943 | ;;; nim-suggest.el ends here 944 | -------------------------------------------------------------------------------- /nim-syntax.el: -------------------------------------------------------------------------------- 1 | ;;; nim-syntax.el --- -*- lexical-binding: t -*- 2 | 3 | ;; 4 | ;; This program is free software; you can redistribute it and/or modify 5 | ;; it under the terms of the GNU General Public License as published by 6 | ;; the Free Software Foundation; either version 2, or (at your option) 7 | ;; any later version. 8 | ;; 9 | ;; This program is distributed in the hope that it will be useful, 10 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | ;; GNU General Public License for more details. 13 | ;; 14 | ;; You should have received a copy of the GNU General Public License 15 | ;; along with this program; see the file COPYING. If not, write to 16 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth 17 | ;; Floor, Boston, MA 02110-1301, USA. 18 | ;; 19 | 20 | ;;; Commentary: 21 | 22 | ;; Sorry, this implementation is pretty much black magic ish... 23 | ;; Maybe understanding Emacs' Syntax Class Table would help. 24 | 25 | ;; my memo: seems like posix_other_consts.nim is good example to test 26 | ;; fortify limitation; adding new face would cause unfinished 27 | ;; fortification. From user's side, they could solve by 28 | ;; `revert-buffer', but I'm not sure this is good approach... 29 | 30 | ;;; Code: 31 | (require 'nim-vars) 32 | (require 'nim-rx) 33 | 34 | (defvar nim-font-lock-keywords 35 | `((,(nim-rx (or line-start ";") (* " ") 36 | defun (+ " ") 37 | (group (or identifier quoted-chars) (* " ") (? (group "*")))) 38 | (1 (if (match-string 2) 39 | 'nim-font-lock-export-face 40 | font-lock-function-name-face) 41 | keep t) 42 | ;; (8 font-lock-type-face keep t) TODO nim-rx needs a proper colon-type expression 43 | ) 44 | 45 | ;; Highlight everything that starts with a capital letter as type. 46 | (,(rx symbol-start (char upper) (* (char alnum "_")) symbol-end) . (0 font-lock-type-face keep)) 47 | 48 | ;; Warning face for tab characters. 49 | (" +" . (0 font-lock-warning-face)) 50 | 51 | ;; This only works if it’s one line 52 | (,(nim-rx (or line-start ";") (* " ") 53 | (or "var" "let" "const" "type") (+ " ") 54 | (group (or identifier quoted-chars) (* " ") (? (group "*")))) 55 | . (1 (if (match-string 2) 56 | 'nim-font-lock-export-face 57 | font-lock-variable-name-face)))) 58 | "Font lock expressions for Nim mode.") 59 | 60 | (defvar nim-font-lock-keywords-extra 61 | `(;; export properties 62 | (,(nim-rx 63 | line-start (1+ " ") 64 | (? "case" (+ " ")) 65 | (group 66 | (or identifier quoted-chars) "*" 67 | (? (and "[" word "]")) 68 | (0+ (and "," (? (0+ " ")) 69 | (or identifier quoted-chars) "*"))) 70 | (0+ " ") (or ":" "{." "=") (0+ nonl) 71 | line-end) 72 | (1 'nim-font-lock-export-face)) 73 | ;; Number literal 74 | (,(nim-rx nim-numbers) 75 | (0 'nim-font-lock-number-face)) 76 | ;; Highlight identifier enclosed by "`" 77 | (nim-backtick-matcher 78 | (10 font-lock-constant-face prepend)) 79 | ;; Highlight $# and $[0-9]+ inside string 80 | (nim-format-$-matcher 0 font-lock-preprocessor-face prepend) 81 | ;; Highlight word after ‘is’ and ‘distinct’ 82 | (,(nim-rx " " (or "is" "distinct") (+ " ") 83 | (group identifier)) 84 | (1 font-lock-type-face)) 85 | ;; pragma 86 | (nim-pragma-matcher . (0 'nim-font-lock-pragma-face))) 87 | "Extra font-lock keywords. 88 | If you feel uncomfortable because of this font-lock keywords, 89 | set nil to this value by ‘nim-mode-init-hook’.") 90 | 91 | (defun nim--convert-to-nim-style-insensitive (str) 92 | (let ((first-str (substring str 0 1)) 93 | (rest-str (substring str 1 (length str)))) 94 | (format "%s_?%s" first-str 95 | (mapconcat 96 | (lambda (s) 97 | (if (string-match "[a-zA-Z]" s) 98 | (format "[%s%s]" (downcase s) (upcase s)) 99 | s)) 100 | (split-string rest-str (rx not-word-boundary)) 101 | "_?")))) 102 | 103 | (defun nim--format-keywords (keywords) 104 | (format "\\_<\\(%s\\)\\_>" 105 | (mapconcat 106 | 'nim--convert-to-nim-style-insensitive 107 | (cl-typecase keywords 108 | (symbol (symbol-value keywords)) 109 | (list keywords)) 110 | "\\|"))) 111 | 112 | (defvar nim-font-lock-keywords-2 113 | (append 114 | (cl-loop 115 | with pairs = `((nim-types . font-lock-type-face) 116 | (nim-variables . font-lock-variable-name-face) 117 | (nim-exceptions . 'error) 118 | (nim-constants . font-lock-constant-face) 119 | (nim-builtin-functions . font-lock-builtin-face) 120 | (nim-nonoverloadable-builtins . 'nim-non-overloadable-face) 121 | (nim-keywords . font-lock-keyword-face)) 122 | for (keywords . face) in pairs 123 | collect (cons (nim--format-keywords keywords) face)) 124 | `((,(rx symbol-start "result" symbol-end) . font-lock-variable-name-face)))) 125 | 126 | (defvar nim-font-lock-keywords-3 127 | (list (cons (nim--format-keywords 'nim-builtins-without-nimscript) 128 | font-lock-builtin-face))) 129 | 130 | (defvar nimscript-keywords 131 | (append 132 | `(,(cons (nim--format-keywords 'nimscript-builtins) 133 | font-lock-builtin-face) 134 | ,(cons (nim--format-keywords 'nimscript-variables) 135 | font-lock-variable-name-face)) 136 | `((,(rx symbol-start "task" symbol-end (1+ " ") 137 | (group symbol-start (or "build" "tests" "bench") symbol-end)) 138 | (1 font-lock-builtin-face)) 139 | ("\\_" (0 font-lock-type-face))))) 140 | 141 | (defsubst nim-syntax-count-quotes (quote-char &optional point limit) 142 | "Count number of quotes around point (max is 3). 143 | QUOTE-CHAR is the quote char to count. Optional argument POINT is 144 | the point where scan starts (defaults to current point), and LIMIT 145 | is used to limit the scan." 146 | (let ((i 0)) 147 | (while (and (< i 3) 148 | (or (not limit) (< (+ point i) limit)) 149 | (eq (char-after (+ point i)) quote-char)) 150 | (setq i (1+ i))) 151 | i)) 152 | 153 | (defconst nim-syntax-propertize-function 154 | (syntax-propertize-rules 155 | ;; single/multi line comment 156 | ((rx (or (group (or line-start (not (any "]#\""))) 157 | (group "#" (? "#") "[")) 158 | (group "]" "#" (? "#")) 159 | (group "#"))) 160 | (0 (ignore (nim-syntax-commentify)))) 161 | ;; Char 162 | ;; Put syntax entry("\"") for character type to highlight 163 | ;; when only the character-delimiter regex matched. 164 | ((nim-rx character-delimiter) 165 | (1 "\"") ; opening quote 166 | (2 "\"")) ; closing quote 167 | ;; String 168 | ((nim-rx string-delimiter) 169 | (0 (ignore (nim-syntax-stringify)))))) 170 | 171 | (defun nim-pretty-triple-double-quotes (pbeg pend &optional close-quote) 172 | (when (and nim-pretty-triple-double-quotes 173 | (bound-and-true-p prettify-symbols-mode)) 174 | (compose-region pbeg pend 175 | (if close-quote 176 | (or (cdr nim-pretty-triple-double-quotes) 177 | (car nim-pretty-triple-double-quotes)) 178 | (car nim-pretty-triple-double-quotes))))) 179 | 180 | (defun nim-syntax--raw-string-p (pos) 181 | "Return non-nil if char of before POS is not word syntax class." 182 | ;; See also #212 183 | (when (> pos 1) 184 | (eq ?w (char-syntax (char-before pos))))) 185 | 186 | (defun nim-syntax-stringify () 187 | "Put `syntax-table' property correctly on single/triple double quotes." 188 | (unless (nth 4 (syntax-ppss)) 189 | (let* ((num-quotes (length (match-string-no-properties 1))) 190 | (ppss (prog2 191 | (backward-char num-quotes) 192 | (syntax-ppss) 193 | (forward-char num-quotes))) 194 | (string-start (and (not (nth 4 ppss)) (nth 8 ppss))) 195 | (quote-starting-pos (- (point) num-quotes)) 196 | (quote-ending-pos (point)) 197 | (num-closing-quotes 198 | (and string-start 199 | (nim-syntax-count-quotes 200 | (char-before) string-start quote-starting-pos)))) 201 | (cond ((and string-start (= num-closing-quotes 0)) 202 | ;; This set of quotes doesn't match the string starting 203 | ;; kind. Do nothing. 204 | nil) 205 | ((not string-start) 206 | ;; This set of quotes delimit the start of a string. 207 | (put-text-property quote-starting-pos (1+ quote-starting-pos) 208 | 'syntax-table (string-to-syntax "|")) 209 | (when (eq num-quotes 3) 210 | (nim-pretty-triple-double-quotes 211 | quote-starting-pos (+ quote-starting-pos 3)))) 212 | ((and string-start (< string-start (- (point) 2)) ;; avoid r"" 213 | (not (eq 3 num-closing-quotes)) 214 | ;; Skip "" in the raw string literal 215 | (nim-syntax--raw-string-p string-start) 216 | (or 217 | ;; v point is here 218 | ;; "" 219 | (and 220 | (eq ?\" (char-before (1- (point)))) 221 | (eq ?\" (char-before (point)))) 222 | ;; v point is here 223 | ;; "" 224 | (and 225 | (eq ?\" (char-before (point))) 226 | (eq ?\" (char-after (point)))))) 227 | nil) 228 | ((= num-quotes num-closing-quotes) 229 | ;; This set of quotes delimit the end of a string. 230 | ;; If there are some double quotes after quote-ending-pos, 231 | ;; shift the point to right number of `extra-quotes' times. 232 | (let* ((extra-quotes 0)) 233 | ;; Only count extra quotes when the double quotes is 3 to prevent 234 | ;; wrong highlight for r"foo""bar" forms. 235 | (when (eq num-quotes 3) 236 | (while (eq ?\" (char-after (+ quote-ending-pos extra-quotes))) 237 | (setq extra-quotes (1+ extra-quotes)))) 238 | ;; #212 Change syntax class of "\" before end of double quote because 239 | ;; Nim support end of "\" in raw string literal. 240 | ;; """str""" will be handled by regex of string delimiter on nim-rx.el 241 | (when (and 242 | (eq num-closing-quotes 1) 243 | (nim-syntax--raw-string-p string-start) 244 | (eq ?\\ (char-after (- (point) 2)))) 245 | (put-text-property (- (point) 2) (1- (point)) 246 | 'syntax-table (string-to-syntax "."))) 247 | 248 | (let ((pbeg (+ (1- quote-ending-pos) extra-quotes)) 249 | (pend (+ quote-ending-pos extra-quotes))) 250 | (put-text-property 251 | pbeg pend 'syntax-table (string-to-syntax "|")) 252 | (when (eq num-quotes 3) 253 | (nim-pretty-triple-double-quotes (- pend 3) pend t))))) 254 | ((> num-quotes num-closing-quotes) 255 | ;; This may only happen whenever a triple quote is closing 256 | ;; a single quoted string. Add string delimiter syntax to 257 | ;; all three quotes. 258 | (put-text-property quote-starting-pos quote-ending-pos 259 | 'syntax-table (string-to-syntax "|"))))))) 260 | 261 | (defun nim-syntax-commentify () 262 | "Put comment syntax property for Nim's single and multi line comment." 263 | (let* ((hash (or (match-string-no-properties 2) 264 | (match-string-no-properties 3) 265 | (match-string-no-properties 4))) 266 | (start-pos (- (point) (length hash))) 267 | (ppss (syntax-ppss)) 268 | (start-len (save-excursion 269 | (when (nth 8 ppss) 270 | (goto-char (nth 8 ppss)) 271 | (looking-at "##?\\[") 272 | (length (match-string 0)))))) 273 | (cond 274 | ;; single line comment 275 | ((and (eq nil (nth 4 ppss)) (eq 1 (length hash))) 276 | ;; comment start 277 | (put-text-property start-pos (1+ start-pos) 278 | 'syntax-table (string-to-syntax "<")) 279 | ;; comment end 280 | ;; #112, make sure ‘comment-indent-new-line’ (C-M-j key) 281 | (put-text-property (point-at-eol) (point-at-eol) 282 | 'syntax-table (string-to-syntax ">"))) 283 | ;; ignore 284 | ((or (eq t (nth 4 ppss)) ; t means single line comment 285 | (<= (length hash) 1) 286 | ;; don't put syntax comment start or end 287 | ;; if it’s "#[" or "]#" inside ##[]## 288 | (and start-len (= 3 start-len) (= 2 (length hash)))) 289 | nil) 290 | ;; multi comment line start 291 | ((eq ?# (string-to-char hash)) 292 | (put-text-property start-pos (1+ start-pos) 293 | 'syntax-table (string-to-syntax "< bn"))) 294 | ;; multi comment line end 295 | ((eq ?\] (string-to-char hash)) 296 | (put-text-property (1- (point)) (point) 297 | 'syntax-table (string-to-syntax "> bn")))))) 298 | 299 | (defun nim-syntax-context-type (&optional syntax-ppss) 300 | "Return the context type using SYNTAX-PPSS. 301 | The type returned can be `comment', `string' or `paren'." 302 | (let ((ppss (or syntax-ppss (syntax-ppss)))) 303 | (cond 304 | ((nth 8 ppss) (if (nth 4 ppss) 'comment 'string)) 305 | ((nth 1 ppss) 'paren)))) 306 | 307 | (defun nim-syntax--context-compiler-macro (form type &optional syntax-ppss) 308 | (pcase type 309 | (`'comment 310 | `(let ((ppss (or ,syntax-ppss (syntax-ppss)))) 311 | (and (nth 4 ppss) (nth 8 ppss)))) 312 | (`'string 313 | `(let ((ppss (or ,syntax-ppss (syntax-ppss)))) 314 | (and (nth 3 ppss) (nth 8 ppss)))) 315 | (`'paren 316 | `(nth 1 (or ,syntax-ppss (syntax-ppss)))) 317 | (_ form))) 318 | 319 | (defun nim-syntax-context (type &optional syntax-ppss) 320 | "Return non-nil if point is on TYPE using SYNTAX-PPSS. 321 | TYPE can be `comment', `string' or `paren'. It returns the start 322 | character address of the specified TYPE." 323 | (declare (compiler-macro nim-syntax--context-compiler-macro)) 324 | (let ((ppss (or syntax-ppss (syntax-ppss)))) 325 | (pcase type 326 | (`comment (and (nth 4 ppss) (nth 8 ppss))) 327 | (`string (and (nth 3 ppss) (nth 8 ppss))) 328 | (`paren (nth 1 ppss)) 329 | (_ nil)))) 330 | 331 | (defsubst nim-syntax-comment-or-string-p (&optional ppss) 332 | "Return non-nil if PPSS is inside 'comment or 'string." 333 | (nth 8 (or ppss (syntax-ppss)))) 334 | 335 | (defsubst nim-syntax-closing-paren-p () 336 | "Return non-nil if char after point is a closing paren." 337 | (= (syntax-class (syntax-after (point))) 338 | (syntax-class (string-to-syntax ")")))) 339 | 340 | ;;;;;;;;;;;;;;;;;;;;;;; 341 | ;; Highlight matcher 342 | 343 | (defun nim-backtick-matcher (&optional limit) 344 | "Highlight matcher for ``symbol`` in comment." 345 | (let (res) 346 | (while 347 | (and 348 | (setq res (re-search-forward 349 | (nim-rx backticks) limit t)) 350 | (not (nth 4 (syntax-ppss))))) 351 | res)) 352 | 353 | (defconst nim--string-interpolation-regex 354 | ;; I think two digit is enough... 355 | (rx "$" (or "#" (and (in "1-9") (? num))))) 356 | 357 | (defun nim-format-$-matcher (&optional limit) 358 | "Highlight matcher for $# and $[1-9][0-9]? in string within LIMIT." 359 | (let (res) 360 | (while 361 | (and 362 | (setq res (re-search-forward 363 | nim--string-interpolation-regex limit t)) 364 | (not (nth 3 (syntax-ppss))))) 365 | res)) 366 | 367 | (defun nim-inside-pragma-p () 368 | (let* ((ppss (syntax-ppss)) 369 | (pos (nth 1 ppss))) 370 | (and 371 | ;; not in comment or string 372 | (not (or (nth 3 ppss) (nth 4 ppss))) 373 | ;; there is an open brace 374 | pos 375 | ;; open brace is curly 376 | (eq ?\{ (char-after pos)) 377 | ;; followed by a dot 378 | (eq ?. (char-after (1+ pos)))))) 379 | 380 | (defconst nim-pragma-regex (nim--format-keywords (mapcar 'car nim-pragmas))) 381 | 382 | (defun nim-pragma-matcher (&optional limit) 383 | "Highlight pragma." 384 | (let (res) 385 | (while 386 | (and 387 | (setq res (re-search-forward 388 | nim-pragma-regex limit t)) 389 | (not (nim-inside-pragma-p)))) 390 | res)) 391 | 392 | (provide 'nim-syntax) 393 | ;;; nim-syntax.el ends here 394 | -------------------------------------------------------------------------------- /nim-util.el: -------------------------------------------------------------------------------- 1 | ;;; nim-util.el --- -*- lexical-binding: t -*- 2 | ;;; Commentary: 3 | ;; 4 | ;;; Code: 5 | (require 'nim-syntax) 6 | 7 | (cl-defun nim-log (&rest msg-and-rest) 8 | (apply #'lwarn 'nim :debug msg-and-rest)) 9 | 10 | (cl-defun nim-log-err (&rest msg-and-rest) 11 | (apply #'lwarn 'nim :error msg-and-rest)) 12 | 13 | (defun nim-util-goto-line (line-number) 14 | "Move point to LINE-NUMBER." 15 | (goto-char (point-min)) 16 | (forward-line (1- line-number))) 17 | 18 | (defun nim-util-forward-comment (&optional direction) 19 | "Nim mode specific version of `forward-comment'. 20 | Optional argument DIRECTION defines the direction to move to." 21 | (let ((comment-start (nim-syntax-context 'comment)) 22 | (factor (if (< (or direction 0) 0) 23 | -99999 24 | 99999))) 25 | (when comment-start 26 | (goto-char comment-start)) 27 | (forward-comment factor))) 28 | 29 | (defun nim-util-backward-stmt () 30 | "Move point backward to the beginning of the current statement. 31 | Point is moved to the beginning of the first symbol that is 32 | either the first on a line or the first after a 33 | semicolon. Balanced parentheses, strings and comments are 34 | skipped." 35 | (let ((level (nth 0 (syntax-ppss)))) 36 | (save-restriction 37 | ;; narrow to surrounding parentheses 38 | (nim-util-narrow-to-paren) 39 | (while (progn 40 | (if (re-search-backward "[,;]" (line-beginning-position) t) 41 | (forward-char) 42 | (beginning-of-line)) 43 | (let ((state (syntax-ppss))) 44 | (and 45 | (or (> (nth 0 state) level) 46 | (nim-syntax-comment-or-string-p state) 47 | (save-match-data 48 | (looking-at (nim-rx (* space) (group operator)))) 49 | (not (looking-at (nim-rx (* space) (group symbol-name))))) 50 | (not (bobp)) 51 | (prog1 t (backward-char)))))) 52 | (and (match-beginning 1) 53 | (goto-char (match-beginning 1)))))) 54 | 55 | (defun nim-util-narrow-to-paren () 56 | "Narrow buffer to content of enclosing parentheses. 57 | Returns non-nil if and only if there are enclosing parentheses." 58 | (save-excursion 59 | (condition-case nil 60 | (prog1 t 61 | (narrow-to-region (progn 62 | (backward-up-list) 63 | (1+ (point))) 64 | (progn 65 | (forward-list) 66 | (1- (point))))) 67 | (scan-error nil)))) 68 | 69 | (defun nim-util-real-current-column () 70 | "Return the current column without narrowing." 71 | (+ (current-column) 72 | (if (= (line-beginning-position) (point-min)) 73 | (save-excursion 74 | (goto-char (point-min)) 75 | (save-restriction 76 | (widen) 77 | (current-column))) 78 | 0))) 79 | 80 | (defun nim-util-real-current-indentation () 81 | "Return the indentation without narrowing." 82 | (+ (current-indentation) 83 | (if (= (line-beginning-position) (point-min)) 84 | (save-excursion 85 | (goto-char (point-min)) 86 | (save-restriction 87 | (widen) 88 | (current-column))) 89 | 0))) 90 | 91 | ;; Stolen from org-mode 92 | (defun nim-util-clone-local-variables (from-buffer &optional regexp) 93 | "Clone local variables from FROM-BUFFER. 94 | Optional argument REGEXP selects variables to clone and defaults 95 | to \"^nim-\"." 96 | (mapc 97 | (lambda (pair) 98 | (and (symbolp (car pair)) 99 | (string-match (or regexp "^nim-") 100 | (symbol-name (car pair))) 101 | (set (make-local-variable (car pair)) 102 | (cdr pair)))) 103 | (buffer-local-variables from-buffer))) 104 | 105 | (defun nim-util-comint-last-prompt () 106 | "Return comint last prompt overlay start and end. 107 | This is for compatibility with Emacs < 24.4." 108 | (cond ((bound-and-true-p comint-last-prompt-overlay) 109 | (cons (overlay-start comint-last-prompt-overlay) 110 | (overlay-end comint-last-prompt-overlay))) 111 | ((bound-and-true-p comint-last-prompt) 112 | comint-last-prompt) 113 | (t nil))) 114 | 115 | (defun nim-util-list-directories (directory &optional predicate max-depth) 116 | "List DIRECTORY subdirs, filtered by PREDICATE and limited by MAX-DEPTH. 117 | Argument PREDICATE defaults to `identity' and must be a function 118 | that takes one argument (a full path) and returns non-nil for 119 | allowed files. When optional argument MAX-DEPTH is non-nil, stop 120 | searching when depth is reached, else don't limit." 121 | (let* ((dir (expand-file-name directory)) 122 | (dir-length (length dir)) 123 | (predicate (or predicate #'identity)) 124 | (to-scan (list dir)) 125 | (tally nil)) 126 | (while to-scan 127 | (let ((current-dir (car to-scan))) 128 | (when (funcall predicate current-dir) 129 | (setq tally (cons current-dir tally))) 130 | (setq to-scan (append (cdr to-scan) 131 | (nim-util-list-files 132 | current-dir #'file-directory-p) 133 | nil)) 134 | (when (and max-depth 135 | (<= max-depth 136 | (length (split-string 137 | (substring current-dir dir-length) 138 | "/\\|\\\\" t)))) 139 | (setq to-scan nil)))) 140 | (nreverse tally))) 141 | 142 | (defun nim-util-list-files (dir &optional predicate) 143 | "List files in DIR, filtering with PREDICATE. 144 | Argument PREDICATE defaults to `identity' and must be a function 145 | that takes one argument (a full path) and returns non-nil for 146 | allowed files." 147 | (let ((dir-name (file-name-as-directory dir))) 148 | (apply #'nconc 149 | (mapcar (lambda (file-name) 150 | (let ((full-file-name (expand-file-name file-name dir-name))) 151 | (when (and 152 | (not (member file-name '("." ".."))) 153 | (funcall (or predicate #'identity) full-file-name)) 154 | (list full-file-name)))) 155 | (directory-files dir-name))))) 156 | 157 | (defun nim-util-list-packages (dir &optional max-depth) 158 | "List packages in DIR, limited by MAX-DEPTH. 159 | When optional argument MAX-DEPTH is non-nil, stop searching when 160 | depth is reached, else don't limit." 161 | (let* ((dir (expand-file-name dir)) 162 | (parent-dir (file-name-directory 163 | (directory-file-name 164 | (file-name-directory 165 | (file-name-as-directory dir))))) 166 | (subpath-length (length parent-dir))) 167 | (mapcar 168 | (lambda (file-name) 169 | (replace-regexp-in-string 170 | (rx (or ?\\ ?/)) "." (substring file-name subpath-length))) 171 | (nim-util-list-directories 172 | (directory-file-name dir) 173 | (lambda (dir) 174 | (file-exists-p (expand-file-name "__init__.py" dir))) 175 | max-depth)))) 176 | 177 | (defun nim-util-popn (lst n) 178 | "Return LST first N elements. 179 | N should be an integer, when negative its opposite is used. 180 | When N is bigger than the length of LST, the list is 181 | returned as is." 182 | (let* ((n (min (abs n))) 183 | (len (length lst)) 184 | (acc)) 185 | (if (> n len) 186 | lst 187 | (while (< 0 n) 188 | (setq acc (cons (car lst) acc) 189 | lst (cdr lst) 190 | n (1- n))) 191 | (reverse acc)))) 192 | 193 | (defun nim-util-strip-string (string) 194 | "Strip STRING whitespace and newlines from end and beginning." 195 | (replace-regexp-in-string 196 | (rx (or (: string-start (* (any whitespace ?\r ?\n))) 197 | (: (* (any whitespace ?\r ?\n)) string-end))) 198 | "" 199 | string)) 200 | 201 | (defun nim-util-valid-regexp-p (regexp) 202 | "Return non-nil if REGEXP is valid." 203 | (ignore-errors (string-match regexp "") t)) 204 | 205 | ;; electric-pair-mode ;; 206 | (defun nim-electric-pair-string-delimiter () 207 | (when (and electric-pair-mode 208 | (memq last-command-event '(?\" ?\')) 209 | (let ((count 0)) 210 | (while (eq (char-before (- (point) count)) last-command-event) 211 | (cl-incf count)) 212 | (= count 3)) 213 | (eq (char-after) last-command-event)) 214 | (save-excursion (insert (make-string 2 last-command-event))))) 215 | 216 | (provide 'nim-util) 217 | ;;; nim-util.el ends here 218 | -------------------------------------------------------------------------------- /nim-vars.el: -------------------------------------------------------------------------------- 1 | ;;; nim-vars.el --- nim-mode's variables -*- lexical-binding: t -*- 2 | 3 | ;; This program is free software; you can redistribute it and/or modify 4 | ;; it under the terms of the GNU General Public License as published by 5 | ;; the Free Software Foundation, either version 3 of the License, or 6 | ;; (at your option) any later version. 7 | 8 | ;; This program is distributed in the hope that it will be useful, 9 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | ;; GNU General Public License for more details. 12 | 13 | ;; You should have received a copy of the GNU General Public License 14 | ;; along with this program. If not, see . 15 | 16 | ;;; Commentary: 17 | 18 | ;; 19 | 20 | ;;; Code: 21 | (defgroup nim nil 22 | "A major mode for the Nim programming language." 23 | :link '(url-link "http://nim-lang.org/") 24 | :group 'languages) 25 | 26 | (defface nim-font-lock-export-face 27 | '((t :weight bold 28 | :slant italic 29 | :inherit font-lock-function-name-face)) 30 | "Font Lock face for export (XXX*)" 31 | :group 'nim) 32 | 33 | (defface nim-font-lock-pragma-face 34 | '((t (:inherit font-lock-preprocessor-face))) 35 | "Font Lock face for pragmas." 36 | :group 'nim) 37 | 38 | (defface nim-non-overloadable-face 39 | '((t :inherit font-lock-builtin-face 40 | :slant italic)) 41 | "Font Lock face for nonoverloadable builtins." 42 | :group 'nim) 43 | 44 | (defface nim-font-lock-number-face 45 | '((t :slant italic)) 46 | "Font Lock face for numbers." 47 | :group 'nim) 48 | 49 | (defcustom nim-indent-trigger-commands 50 | '(indent-for-tab-command yas-expand yas/expand) 51 | "Commands that might trigger a `nim-indent-line' call." 52 | :type '(repeat symbol) 53 | :group 'nim) 54 | 55 | (defcustom nim-indent-offset 2 56 | "Number of spaces per level of indentation." 57 | :type 'integer 58 | :group 'nim) 59 | 60 | (defcustom nim-smie-function-indent 4 61 | "Number of spaces between ‘proc ... =’. 62 | Note that this configuration affects other ‘template’, ‘macro’, 63 | ‘iterator’, and ‘converter’ declaration as well." 64 | :type 'integer 65 | :group 'nim) 66 | 67 | (defcustom nim-smie-indent-stoppers 68 | '("proc" "func" "template" "macro" "iterator" "converter" "type") 69 | "Indentation behavior after empty line. 70 | You can specify list of string, which you want to stop indenting. 71 | If it’s nil, it does nothing." 72 | :type '(choice 73 | (repeat :tag "" string) 74 | (const :tag "" nil)) 75 | :group 'nim) 76 | 77 | (defcustom nim-smie-indent-dedenters 'all-dedent 78 | "Indentation behavior after empty line. 79 | If you set ‘all-dedent’, it forces dedent whatever point starts. 80 | Or you can specify list of string, which you want to dedent. 81 | If it’s nil, it does nothing." 82 | :type '(choice 83 | (repeat :tag "Dedenter symbols" string) 84 | (const :tag "Don't dedent" nil) 85 | (const :tag 86 | "Dedent all if previous line is empty line" all-dedent)) 87 | :group 'nim) 88 | 89 | (defcustom nim-smie-dedent-after-break '() 90 | "List of string that dedent after break statement. 91 | This feature is activated if only the break line has 92 | other tokens like ’:’ or ’=’." 93 | :type '(choise 94 | (repeat :tag "List of dedenter token" string) 95 | (const :tag "" nil))) 96 | 97 | (defcustom nim-smie-after-indent-hook nil 98 | "Hook run after indenting." 99 | :type 'hook 100 | :group 'nim) 101 | 102 | (defcustom nim-mode-init-hook nil 103 | "This hook is called when ‘nim-mode’ is initialized." 104 | :type 'hook 105 | :group 'nim) 106 | 107 | (defcustom nim-common-init-hook nil 108 | "A hook for both nim-mode and nimscript-mode." 109 | :type 'hook 110 | :group 'nim) 111 | 112 | (defcustom nim-capf-after-exit-function-hook nil 113 | "A hook that is called with an argument. 114 | The argument is string that has some properties." 115 | :type 'hook 116 | :group 'nim) 117 | 118 | (defcustom nim-pretty-triple-double-quotes 119 | ;; What character should be default? („…“, “…”, ‘…’, or etc.?) 120 | (cons ?„ ?”) 121 | "Change triple double quotes to another quote form. 122 | This configuration is enabled only in `prettify-symbols-mode`." 123 | :type 'cons 124 | :group 'nim) 125 | 126 | (defcustom nim-compile-command "nim" 127 | "Path to the nim executable. 128 | You don't need to set this if the nim executable is inside your PATH." 129 | :type 'string 130 | :group 'nim) 131 | 132 | (defcustom nim-compile-user-args '() 133 | "The arguments to pass to `nim-compile-command' to compile a file." 134 | :type '(repeat string) 135 | :group 'nim) 136 | 137 | (defcustom nimsuggest-path (executable-find "nimsuggest") 138 | "Path to the nimsuggest binary." 139 | :type '(choice (const :tag "Path of nimsuggest binary" string) 140 | (const :tag "" nil)) 141 | :group 'nim) 142 | 143 | (defcustom nimsuggest-options '("--refresh") 144 | "Command line options for Nimsuggest. 145 | ‘--epc’ are automatically passed to nim-mode’s EPC (Emacs RPC) function." 146 | :type '(choice (repeat :tag "List of options" string) 147 | (const :tag "" nil)) 148 | :group 'nim) 149 | 150 | (defcustom nimsuggest-local-options '() 151 | "Options for Nimsuggest. 152 | Please use this variable to set nimsuggest’s options for 153 | specific directory or buffer. See also ‘dir-locals-file’." 154 | :type '(choice (repeat :tag "List of options" string) 155 | (const :tag "" nil)) 156 | :group 'nim) 157 | 158 | (defcustom nimsuggest-dirty-directory 159 | ;; Even users changed the temp directory name, 160 | ;; ‘file-name-as-directory’ ensures suffix directory separator. 161 | (mapconcat 'file-name-as-directory 162 | `(,temporary-file-directory "emacs-nim-mode") "") 163 | "Directory name, which nimsuggest uses temporarily. 164 | Note that this directory is removed when you exit from Emacs." 165 | :type 'directory 166 | :group 'nim) 167 | 168 | (defcustom nimsuggest-accept-process-delay 150 169 | "Number of delay msec to check nimsuggest epc connection is established." 170 | :type 'integer 171 | :group 'nim) 172 | 173 | (defcustom nimsuggest-accept-process-timeout-count 100 174 | "Number of count that Emacs can try to check nimsuggest epc connection. 175 | For example, if you set `nimsuggest-accept-process-delay' to 150 and this value 176 | was 100, the total time of timeout for nimsuggest epc connection would be about 177 | 15sec." 178 | :type 'integer 179 | :group 'nim) 180 | 181 | (defcustom nimsuggest-show-doc-function 'nimsuggest--show-doc-rst 182 | "The style used to display Nim documentation. 183 | Options available are `nimsuggest--show-doc-rst' and `nimsuggest--show-doc-org'. 184 | Note `nimsuggest--show-doc-org' enables syntax highlighting and section folding, 185 | but is experimental." 186 | :type 'function 187 | :group 'nim) 188 | 189 | 190 | (defvar nimsuggest-eldoc-function 'ignore) ; #208 191 | (defvar nimsuggest-ignore-dir-regex 192 | (rx (or "\\" "/") (in "nN") "im" (or "\\" "/") "compiler" (or "\\" "/"))) 193 | (defvar nim--inside-compiler-dir-p nil) 194 | 195 | 196 | ;; Keymaps 197 | 198 | ;; Supported basic keybinds: 199 | ;; C means Control-key 200 | ;; M means Meta-key 201 | ;; C-M-a -- jump to head of proc 202 | ;; C-M-e -- jump to end of proc 203 | ;; C-M-h -- mark region of function 204 | 205 | (defvar nim-mode-map 206 | (let ((map (make-sparse-keymap))) 207 | ;; Allowed keys: C-c with control-letter, or {,}, <, >, :, ; 208 | ;; See also: http://www.gnu.org/software/emacs/manual/html_node/elisp/Key-Binding-Conventions.html 209 | (define-key map (kbd "C-c C-c") 'nim-compile) 210 | (define-key map (kbd "C-c <") 'nim-indent-shift-left) 211 | (define-key map (kbd "C-c >") 'nim-indent-shift-right) 212 | ;; TODO: 213 | ;; C-c C-j - imemu 214 | ;; implement mark-defun 215 | ;; 216 | map)) 217 | 218 | (defvar nimsuggest-doc-mode-map 219 | (let ((map (make-sparse-keymap))) 220 | (cond 221 | ((and (eq nimsuggest-show-doc-function 'nimsuggest--show-doc-org) 222 | (fboundp 'org-mode)) 223 | (set-keymap-parent map (make-composed-keymap org-mode-map)) 224 | (define-key map (kbd "RET") 'org-open-at-point)) 225 | (t (set-keymap-parent map (make-composed-keymap special-mode-map)))) 226 | (define-key map (kbd ">") 'nimsuggest-doc-next) 227 | (define-key map (kbd "<") 'nimsuggest-doc-previous) 228 | (define-key map (kbd "q") 'quit-window) 229 | map) 230 | "Nimsuggest doc mode keymap.") 231 | 232 | ;; xref supported key binds: 233 | ;; esc-map "." xref-find-definitions 234 | ;; esc-map "," xref-pop-marker-stack 235 | ;; esc-map "?" xref-find-references 236 | ;; ctl-x-4-map "." xref-find-definitions-other-window 237 | ;; ctl-x-5-map "." xref-find-definitions-other-frame 238 | ;; TODO: esc-map [?\C-.] xref-find-apropos 239 | 240 | ;; hs-minor-mode: 241 | ;; C-c @ C-M-h -- hide/fold functions 242 | ;; C-c @ C-M-s -- show functions 243 | ;; (You can do same thing by zr and zm keys on evil, a vim emulation plugin) 244 | 245 | 246 | ;; Syntax table 247 | (defvar nim-mode-syntax-table 248 | (let ((table (make-syntax-table))) 249 | ;; Give punctuation syntax to ASCII that normally has symbol 250 | ;; syntax or has word syntax and isn't a letter. 251 | (let ((symbol (string-to-syntax "_")) 252 | (sst (standard-syntax-table))) 253 | (dotimes (i 128) 254 | (unless (= i ?_) 255 | (if (equal symbol (aref sst i)) 256 | (modify-syntax-entry i "." table))))) 257 | (modify-syntax-entry ?$ "." table) 258 | (modify-syntax-entry ?% "." table) 259 | 260 | ;; Comment 261 | (modify-syntax-entry ?# "." table) 262 | (modify-syntax-entry ?\n ">" table) 263 | ;; Use "." Punctuation syntax class because I got error when I 264 | ;; used "$" from smie.el 265 | (modify-syntax-entry ?` "'" table) 266 | 267 | ;; Use _ syntax to single quote 268 | ;; See also `nim-syntax-propertize-function'. 269 | (modify-syntax-entry ?\' "_" table) 270 | 271 | ;; Parentheses 272 | (modify-syntax-entry ?\[ "(] " table) 273 | (modify-syntax-entry ?\] ")[ " table) 274 | (modify-syntax-entry ?\{ "(} " table) 275 | (modify-syntax-entry ?\} "){ " table) 276 | (modify-syntax-entry ?\( "() " table) 277 | (modify-syntax-entry ?\) ")( " table) 278 | table) 279 | "Syntax table for Nim files.") 280 | 281 | (defvar nim-dotty-syntax-table 282 | (let ((table (copy-syntax-table nim-mode-syntax-table))) 283 | (modify-syntax-entry ?. "w" table) 284 | (modify-syntax-entry ?_ "w" table) 285 | table) 286 | "Dotty syntax table for Nim files. 287 | It makes underscores and dots word constituent chars.") 288 | 289 | (defvar nimscript-mode-syntax-table 290 | (copy-syntax-table nim-mode-syntax-table) 291 | "Syntax table for NimScript files.") 292 | 293 | (defconst nim-comment 294 | `((single 295 | . ((comment-start . "#") 296 | (comment-end . "") 297 | (comment-start-skip . ,(rx "#" (? "#") (? " "))) 298 | (comment-use-syntax . t))) 299 | (multi 300 | . ((comment-start . "#[") 301 | (comment-end . "]#") 302 | (comment-start-skip 303 | . ,(rx (group 304 | (syntax comment-start) (? "#") "["))) 305 | (comment-end-skip 306 | . ,(rx (group 307 | "]#" (? "#")))) 308 | ;; comment-continue has to include non space character 309 | ;; otherwise it makes trouble when you do ‘uncomment-region’. 310 | (comment-continue . " |") 311 | (comment-padding . " ") 312 | (comment-multi-line . t) 313 | (comment-use-syntax . nil))))) 314 | 315 | 316 | ;; Nim keywords 317 | 318 | ;; Those keywords are used to syntax highlight as well as 319 | ;; auto-completion. If you want to change those keywords, 320 | ;; please consider about auto-completion; it inserts what 321 | ;; you registered. (snark or camel) 322 | 323 | (defconst nim-keywords 324 | '("addr" "and" "as" "asm" "atomic" "bind" "block" "break" "case" 325 | "cast" "concept" "const" "continue" "converter" "defer" "discard" "distinct" 326 | "div" "do" "elif" "else" "end" "enum" "except" "export" "finally" "for" 327 | "func" "from" "generic" "if" "import" "in" "include" "interface" "isnot" 328 | "iterator" "lambda" "let" "macro" "method" "mixin" "mod" "nil" "not" 329 | "notin" "object" "of" "or" "out" "proc" "ptr" "raise" "ref" "return" 330 | "shared" "shl" "shr" "static" "template" "try" "tuple" "type" "unsafeAddr" 331 | "using" "var" "when" "while" "with" "without" "xor" "yield") 332 | "Nim keywords. 333 | The above string is taken from URL 334 | `https://nim-lang.org/docs/manual.html#lexical-analysis-identifiers-amp-keywords' 335 | for easy updating.") 336 | 337 | (defconst nim-types 338 | '("int" "int8" "int16" "int32" "int64" "uint" "uint8" "uint16" "uint32" 339 | "uint64" "float" "float32" "float64" "bool" "char" "string" 340 | "pointer" "typedesc" "void" "auto" "any" "sink" "lent" 341 | "untyped" "typed" "range" "array" "openArray" "varargs" "seq" "set" "byte" 342 | ;; c interop types 343 | "cchar" "cschar" "cshort" "cint" "clong" "clonglong" "cfloat" "cdouble" 344 | "cstring" "clongdouble" "cstringArray" "csize" "csize_t" "cuchar" "cuint" 345 | "culong" "culonglong" "cushort") 346 | "Nim types defined in .") 347 | 348 | (defconst nim-exceptions 349 | '("Exception" "SystemError" "IOError" "OSError" "LibraryError" 350 | "ResourceExhaustedError" "ArithmeticError" "DivByZeroError" "OverflowError" 351 | "AccessViolationError" "AssertionError" "ValueError" "KeyError" 352 | "OutOfMemError" "IndexError" "FieldError" "RangeError" 353 | "StackOverflowError" "ReraiseError" "ObjectAssignmentError" 354 | "ObjectConversionError" "DeadThreadError" "FloatInexactError" 355 | "FloatUnderflowError" "FloatingPointError" "FloatInvalidOpError" 356 | "FloatDivByZeroError" "FloatOverflowError") 357 | "Nim exceptions defined in .") 358 | 359 | (defconst nim-variables 360 | '("programResult" "globalRaiseHook" "localRaiseHook" "outOfMemHook" 361 | ;; not nimscript 362 | "stdin" "stdout" "stderr") 363 | "Nim variables defined in .") 364 | 365 | (defconst nim-constants 366 | '("isMainModule" "CompileDate" "CompileTime" "NimVersion" 367 | "NimMajor" "NimMinor" "NimPatch" "NimStackTrace" "cpuEndian" "hostOS" 368 | "hostCPU" "appType" "Inf" "NegInf" "NaN" "nimvm" "QuitSuccess" 369 | "QuitFailure" "true" "false" "nil" 370 | "on" "off" "NoFakeVars") 371 | "Nim constants defined in .") 372 | 373 | (defconst nim-nonoverloadable-builtins 374 | '("declared" "defined" "definedInScope" "compiles" "low" "high" "sizeOf" 375 | "is" "of" "shallowCopy" "getAst" "astToStr" "spawn" "procCall") 376 | "Nim nonoverloadable builtins.") 377 | 378 | (defconst nim-builtin-functions 379 | '("div" "mod" "shr" "shl" "and" "or" "xor" 380 | "not" "notin" "isnot" "cmp" "succ" "pred" "inc" 381 | "dec" "newseq" "len" "xlen" "incl" "excl" "card" "ord" "chr" "ze" "ze64" 382 | "toU8" "toU16" "toU32" "min" "max" "setLen" "newString" "add" 383 | "compileOption" "del" "delete" "insert" "repr" "toFloat" 384 | "toBiggestFloat" "toInt" "toBiggestInt" "addQuitProc" "copy" 385 | "slurp" "staticRead" "gorge" "staticExec" "instantiationInfo" 386 | "currentSourcePath" "raiseAssert" "failedAssertImpl" "assert" "doAssert" 387 | "onFailedAssert" "shallow" "eval" "locals" 388 | "swap" "getRefcount" "countdown" "countup" "min" "max" "abs" "clamp" 389 | "items" "mitems" "pairs" "mpairs" "isNil" 390 | "find" "contains" "pop" "fields" "fieldPairs" "each" 391 | "accumulateresult" "echo" "debugEcho" "newException" 392 | "getTypeInfo" "quit" "open" "reopen" "close" "endOfFile" 393 | "readChar" "flushFile" "readAll" "readFile" "write" "writeFile" 394 | "readLine" "writeLn" "writeLine" 395 | "getFileSize" "readBytes" "readChars" "readBuffer" "writeBytes" 396 | "writeChars" "writeBuffer" "setFilePos" "getFilePos" "getFileHandle" 397 | "lines" "cstringArrayToSeq" "getDiscriminant" "selectBranch" 398 | ;; hasAlloc 399 | "safeAdd") 400 | "Standard library functions fundamental enough to count as builtins. 401 | Magic functions.") 402 | 403 | (defconst nim-builtins-without-nimscript 404 | '(;; hasAlloc && not nimscript && not JS 405 | "deepCopy" 406 | ;; not nimscirpt 407 | "zeroMem" "copyMem" "moveMem" "equalMem" 408 | ;; not nimscirpt && hasAlloc 409 | "alloc" "createU" "alloc0" "create" "realloc" "resize" "dealloc" 410 | "allocShared" "createShareU" "allocShared0" "createShared" 411 | "reallocShared" "resizeShared" "deallocShared" "freeShared" 412 | "getOccupiedMem" "getFreeMem" "getTotalMem" 413 | "GC_disable" "GC_enable" "GC_fullCollect" "GC_setStrategy" 414 | "GC_enableMarkAndSweep" "GC_disableMarkAndSweep" 415 | "GC_getStatistics" "GC_ref" "GC_unref" 416 | ;; not nimscirpt && hasAlloc && hasThreadSupport 417 | "getOccupiedSharedMem" "getFreeSharedMem" "getTotalSharedMem" 418 | ;; not nimscirpt && Not JS 419 | "likely" "unlikely" "rawProc" "rawEnv" "finished" 420 | ;; not nimscirpt && not hostOS "standalone" && Not JS 421 | "getCurrentException" "getCurrentExceptionMsg" "onRaise" 422 | "setCurrentException") 423 | "Builtin functions copied from system.nim. 424 | But all those functions can not use in NimScript.") 425 | 426 | ;; Nimscript 427 | (defvar nim-nimble-ini-format-regex (rx line-start "[Package]")) 428 | 429 | (defconst nimscript-builtins 430 | '("listDirs" "listFiles" "paramStr" "paramCount" "switch" "getCommand" 431 | "setCommand" "cmpic" "getEnv" "existsEnv" "fileExists" "dirExists" 432 | "existsFile" "existsDir" "toExe" "toDll" "rmDir" "rmFile" "mkDir" 433 | "mvFile" "cpFile" "exec" "put" "get" "exists" "nimcacheDir" "thisDir" 434 | "cd" "requires" 435 | ;; templates 436 | "--" "withDir" "task")) 437 | 438 | (defconst nimscript-variables 439 | '("packageName" "version" "author" "description" "license" 440 | "srcDir" "binDir" "backend" "mode" "skipDirs" "skipFiles" 441 | "skipExt" "installDirs" "installFiles" "installExt" "bin" 442 | "requiresData")) 443 | 444 | (defconst nim-pragmas 445 | ;; alist of (PRAGMA_NAME . DESCRIPTION) 446 | ;; the DESCRIPTION can be either a string or list of string. 447 | ;; Use list of string when you want to describe a pragma, but there 448 | ;; are more than two ways to use. I.e, pure pragma 449 | ;; (though I don't implemented displaying list of string yet) 450 | '(("deprecated" . 451 | ("deprecate a symbol" 452 | "[OLD: NEW, ...] -- deprecate symbols")) 453 | ("noSideEffect" . 454 | "used to mark a proc/iterator to have no side effects") 455 | ("procvar" . 456 | "used to mark a proc that it can be passed to a procedural variable.") 457 | ("destructor" . 458 | "used to mark a proc to act as a type destructor. [duplicated]") 459 | ("compileTime" . 460 | "used to mark a proc or variable to be used at compile time only") 461 | ("noReturn" . 462 | "The ``noreturn`` pragma is used to mark a proc that never returns") 463 | ("acyclic" . 464 | "can be used for object types to mark them as acyclic") 465 | ("final" . 466 | "can be used for an object type to specify that it cannot be inherited from.") 467 | ("pure" . 468 | ("object type can be marked to its type field, which is used for runtime type identification is omitted" 469 | "enum type can be marked to access of its fields always requires full qualification")) 470 | ("shallow" . 471 | "affects the semantics of a type: The compiler is allowed to make a shallow copy.") 472 | ("asmNoStackFrame" . 473 | "A proc can be marked with this pragma to tell the compiler it should not generate a stack frame for the proc.") 474 | ("error" . 475 | ("used to make the compiler output an error message with the given content." 476 | "can be used to annotate a symbol (like an iterator or proc)")) 477 | ("fatal" . 478 | "In contrast to the error pragma, compilation is guaranteed to be aborted by this pragma.") 479 | ("warning" . 480 | "is used to make the compiler output a warning message with the given content. Compilation continues after the warning.") 481 | ("hint" . 482 | "is used to make/disable the compiler output a hint message with the given content.") 483 | ("line" . 484 | "can be used to affect line information of the annotated statement as seen in stack backtraces") 485 | ("linearScanEnd" . 486 | "can be used to tell the compiler how to compile a Nim `case`:idx: statement. Syntactically it has to be used as a statement") 487 | ("computedGoto". 488 | "can be used to tell the compiler how to compile a Nim `case`:idx: in a ``while true`` statement.") 489 | ("unroll" . 490 | "can be used to tell the compiler that it should unroll a `for`:idx: or `while`:idx: loop for runtime efficiency") 491 | ("register". 492 | "declares variable as register, giving the compiler a hint that the variable should be placed in a hardware register for faster access.") 493 | ("global" . 494 | "can be applied to a variable within a proc to instruct the compiler to store it in a global location and initialize it once at program startup.") 495 | ("deadCodeElim" . 496 | "on -- tells the compiler to activate (or deactivate) dead code elimination for the module the pragma appears in.") 497 | ("noForward" . 498 | "on|off -- no forward declaration") 499 | ("pragma" . 500 | "can be used to declare user defined pragmas.") 501 | ("experimental" . 502 | "enables experimental language features.") 503 | ("push" . 504 | "are used to override the settings temporarily with pop") 505 | ("pop" . 506 | "are used to override the settings temporarily with push") 507 | ;; implementation specific pragmas 508 | ("bitsize" . 509 | "is for object field members. It declares the field as a bitfield in C/C++.") 510 | ("volatile" . 511 | "is for variables only. It declares the variable as `volatile`, whatever that means in C/C++.") 512 | ("noDecl" . 513 | "tell Nim that it should not generate a declaration for the symbol in the C code.") 514 | ("header" . 515 | "can be applied to almost any symbol and specifies that it should not be declared and instead the generated code should contain an `#include`") 516 | ("incompleteStruct" . 517 | "tells the compiler to not use the underlying C ``struct`` in a ``sizeof`` expression") 518 | ("compile" . 519 | "STRING -- can be used to compile and link a C/C++ source file with the project") 520 | ("link" . 521 | "STRING -- can be used to link an additional file with the project") 522 | ("passC" . 523 | ("STRING -- can be used to pass additional parameters to the C compiler like commandline switch `--passC`" 524 | "you can use `gorge` from the `system module` to embed parameters from an external command at compile time")) 525 | ("passL" . 526 | ("STRING -- can be used to pass additional parameters to the linker like commandline switch `--passL`" 527 | "you can use `gorge` from the `system module` to embed parameters from an external command at compile time")) 528 | ("emit" . 529 | "STRING -- can be used to directly affect the output of the compiler's code generator.") 530 | ("importcpp" . "") 531 | ("importobjc" . "") 532 | ("codegenDecl" . 533 | "STRING -- can be used to directly influence Nim's code generator.") 534 | ("injectStmt" . 535 | "can be used to inject a statement before every other statement in the current module.") 536 | ("intdefine" . "Reads in a build-time define as an integer") 537 | ("strdefine" . "Reads in a build-time define as a string") 538 | ;; Compilation option pragmas 539 | ("checks" . 540 | "on|off -- Turns the code generation for all runtime checks on or off.") 541 | ("boundChecks" . 542 | "on|off -- Turns the code generation for array bound checks on or off.") 543 | ("overflowChecks" . 544 | "on|off -- Turns the code generation for over- or underflow checks on or off.") 545 | ("nilChecks" . 546 | "on|off -- Turns the code generation for nil pointer checks on or off.") 547 | ("assertions" . 548 | "on|off -- Turns the code generation for assertions on or off.") 549 | ("warnings" . 550 | "on|off -- Turns the warning messages of the compiler on or off.") 551 | ("hints" . 552 | "on|off -- Turns the hint messages of the compiler on or off.") 553 | ("optimization" . 554 | "none|speed|size -- optimize the code for the options") 555 | ("callconv" . 556 | "cdecl|... -- Specifies the default calling convention for all procedures (and procedure types) that follow.") 557 | ;; ffi.txt 558 | ("importc" . "STRING") 559 | ("exportc" . "STRING") 560 | ("extern" . "STRING") 561 | ("bycopy" . "can be applied to an object or tuple type and instructs the compiler to pass the type by value to procs") 562 | ("byref" . "can be applied to an object or tuple type and instructs the compiler to pass the type by reference (hidden pointer) to procs.") 563 | ("varargs" . "tells Nim that the proc can take a variable number of parameters after the last specified parameter.") 564 | ("union" . "can be applied to any ``object`` type. It means all of the object's fields are overlaid in memory.") 565 | ("packed" . "ensures that the fields of an object are packed back-to-back in memory.") 566 | ("unchecked" . "can be used to mark a named array as `unchecked` meaning its bounds are not checked.") 567 | ("dynlib" . "STRING -- dynamic library") 568 | ;; threads.txt 569 | ("thread" . "") 570 | ("threadvar" . "") 571 | ;; locking.txt 572 | ("guard" . "") 573 | ("locks" . "[X, ...]") 574 | ;; effects.txt 575 | ("raises" . "[EXCEPTION, ...] -- can be used to explicitly define which exceptions a proc/iterator/method/converter is allowed to raise.") 576 | ("tags" . "[TYPE, ...]") 577 | ("effects" . "") 578 | ;; types.txt 579 | ("nimcall" . 580 | "default convention used for a Nim proc. It is the same as `fastcall`, but only for C compilers that support `fastcall`") 581 | ("closure" . 582 | "default calling convention for a procedural type that lacks any pragma annotations.") 583 | ("stdcall" . 584 | "convention as specified by Microsoft; the generated C procedure is declared with `__stdcall` keyword.") 585 | ("cdecl" . 586 | "The cdecl convention means that a procedure shall use the same convention as the C compiler.") 587 | ("safecall" . "") 588 | ("inline" . "") 589 | ("fastcall" . "means different things to different C compilers. One gets whatever the C `__fastcall` means.") 590 | ("syscall" . "syscall convention is the same as `__syscall` in C. It is used for interrupts.") 591 | ("noconv" . "generated C code will not have any explicit calling convention and thus use the C compiler's default calling convention.") 592 | ("inheritable" . "introduce new object roots apart from `system.RootObj`") 593 | ;; http://forum.nim-lang.org/t/1100 594 | ;; copied Araq's explanation 595 | ("gensym" . "generate a fresh temporary variable here for every instantiation to resemble function call semantics") 596 | ("dirty" . "everything is resolved in the instantiation context") 597 | ("inject" . "the instantiation scope sees this symbol") 598 | ("immediate" . "don't resolve types and expand this thing eagerly") 599 | ;; http://nim-lang.org/docs/manual.html#statements-and-expressions-var-statement 600 | ("noInit" . "avoid implicit initialization for `var`") 601 | ("requiresInit" . "avoid implicit initialization for `var` and it does a control flow analysis to prove the variable has been initialized and does not rely on syntactic properties") 602 | ;; http://nim-lang.org/docs/manual.html#types-pre-defined-floating-point-types 603 | ("NanChecks" . "on|off") 604 | ("InfChecks" . "on|off") 605 | ("floatChecks" "on|off") 606 | ;;; not sure where to look 607 | ("noinline" . "") 608 | ("benign" . "") 609 | ("profiler" . "") 610 | ("stackTrace" . "") 611 | ("sideEffect" . "") 612 | ("compilerRtl" . "") 613 | ("merge" . "") 614 | ("gcsafe" . "") 615 | ("rtl" . "") 616 | ;; from 14.0 617 | ("this" . "self|ID -- automatic self insertion") 618 | ;; seems related to this: http://nim-lang.org/docs/intern.html#how-the-rtl-is-compiled 619 | ;; but not sure... 620 | ("compilerProc" . "") 621 | ("magic" . "compiler intrinsics") 622 | ) 623 | "Alist of (pragma name . description). 624 | The description is unofficial; PRs are welcome.") 625 | 626 | (defconst nim-environment-variables 627 | '(; from unittest.nim 628 | "NIMTEST_OUTPUT_LVL" "NIMTEST_NO_COLOR" "NIMTEST_ABORT_ON_ERROR")) 629 | 630 | 631 | ;; obsolete 632 | (make-obsolete 633 | 'nim-tab-face 634 | "The nim-tab-face was obsoleted, use `white-space-mode' instead to highlight tabs." 635 | "Oct/20/2017") 636 | 637 | ;; Added Oct 17, 2017 638 | (define-obsolete-variable-alias 'nim-nimsuggest-path 'nimsuggest-path "Oct/20/2017") 639 | (define-obsolete-variable-alias 'nim-dirty-directory 'nimsuggest-dirty-directory "Oct/20/2017") 640 | (define-obsolete-variable-alias 'nim-suggest-options 'nimsuggest-options "Oct/23/2017") 641 | (define-obsolete-variable-alias 'nim-suggest-local-options 'nimsuggest-local-options "Oct/23/2017") 642 | (define-obsolete-variable-alias 'nim-suggest-ignore-dir-regex 'nimsuggest-ignore-dir-regex "Oct/23/2017") 643 | (define-obsolete-variable-alias 'nim-inside-compiler-dir-p 'nim--inside-compiler-dir-p "Oct/23/2017") 644 | 645 | (provide 'nim-vars) 646 | ;;; nim-vars.el ends here 647 | -------------------------------------------------------------------------------- /starterKit.nims: -------------------------------------------------------------------------------- 1 | # Just for Emacs beginners to check nim-mode's taste. 2 | # or could be beneficial to check manually something in bare Emacs environment? 3 | 4 | # You can execute this file by `nim e starterKit.nims`. 5 | 6 | import strutils, ospaths 7 | 8 | const caskInstallCommand = 9 | "curl -fsSL https://raw.githubusercontent.com/cask/cask/master/go" & 10 | "| python" 11 | 12 | if "" == findExe("cask"): 13 | echo "Install Cask (a package manager for Emacs)" 14 | exec caskInstallCommand 15 | 16 | if dirExists(thisDir() / ".cask"): 17 | exec "cask install" 18 | 19 | const nimCode = """#[ 20 | nim-mode's specific keybinds: 21 | ############################# 22 | 23 | C means Control-key 24 | M means Meta-key 25 | C-M-a -- jump to head of proc 26 | C-M-e -- jump to end of proc 27 | C-M-h -- mark region of function 28 | 29 | After M-x hs-minor-mode 30 | ####################### 31 | 32 | C-c @ C-M-h -- hide/fold functions 33 | C-c @ C-M-s -- show functions 34 | 35 | Nimsuggest related commands 36 | ########################### 37 | (this needs extra configuration. See also README.md) 38 | 39 | M-. -- jump to definition 40 | ]# 41 | proc foo() = 42 | echo 'a' & 'b' & 'c' 43 | echo astToStr(bar) 44 | 45 | # Note that you can close the current Emacs buffer with C-x C-c. 46 | """.split(NewLines).join("\\n") 47 | 48 | const emacsConfig = """" 49 | (progn 50 | (unless (version<= \"24.4\" emacs-version) 51 | (error \"nim-mode needs emacs version 24.4 or later\")) 52 | (require 'nim-mode) 53 | (nim-mode) 54 | (setq initial-scratch-message $#)) 55 | """".split(NewLines).join(" ") 56 | 57 | const scratchBuffer = emacsConfig.format("\\\"" & nimCode & "\\\"") 58 | 59 | proc startEmacs() = 60 | exec "cask exec emacs -Q -L . --eval " & scratchBuffer 61 | 62 | startEmacs() 63 | -------------------------------------------------------------------------------- /tests/helper/test-syntax-helper.el: -------------------------------------------------------------------------------- 1 | ;;; test-helper.el --- -*- lexical-binding: t; -*- 2 | 3 | (require 'nim-mode) 4 | (require 'cl-lib) 5 | 6 | ;; Test functions 7 | (defun test-concat-dir (filepath) 8 | (if noninteractive 9 | filepath 10 | (defvar-local test-syntax-dir (concat (locate-dominating-file buffer-file-name ".git"))) 11 | (concat test-syntax-dir filepath))) 12 | 13 | (defun prepare-file (file) 14 | (when file 15 | (insert-file-contents-literally (test-concat-dir (concat "tests/syntax/" file))) 16 | (font-lock-default-fontify-buffer))) 17 | 18 | (cl-defun face-at-point-of (pos &key on) 19 | "Return point of the face." 20 | (prepare-file on) 21 | (get-text-property pos 'face)) 22 | 23 | (cl-defun range-of-faces-between (pos1 pos2 &key on) 24 | "Return face between POS1 and POS2." 25 | (prepare-file on) 26 | (cl-loop for i from pos1 to pos2 27 | collect (get-text-property i 'face))) 28 | 29 | (defun the-range-of (faces) 30 | (1- (length faces))) 31 | 32 | (defun diff-of (pos1 pos2) 33 | (- (max pos1 pos2) (min pos1 pos2))) 34 | 35 | (cl-defun assert-highlight-between (pos1 pos2 &key on notmatch match) 36 | (let ((faces 37 | (cl-loop with fs = (range-of-faces-between pos1 pos2 :on on) 38 | with expected-face = match 39 | for face in (delq nil fs) 40 | if (or (eq expected-face face) 41 | (member expected-face face)) 42 | ;; face property can have more than two faces 43 | collect expected-face 44 | else collect 'unexpected-face))) 45 | (if notmatch 46 | (expect faces :not :to-contain notmatch) 47 | (expect faces :to-contain match) 48 | (expect (the-range-of faces) :to-be (diff-of pos1 pos2))) 49 | (expect faces :not :to-contain 'unexpected-face))) 50 | 51 | (cl-defun assert-highlights-between (ranges &key on notmatch match) 52 | (cl-loop for (pos1 . pos2) in ranges 53 | for (kwd . res) = (if notmatch 54 | (cons :notmatch notmatch) 55 | (cons :match match)) 56 | do (assert-highlight-between pos1 pos2 :on on kwd res))) 57 | 58 | (defun check-highlight (string) 59 | (goto-char (point-min)) 60 | (if (search-forward string nil t) 61 | (assert-highlight-between 62 | (point) (1- (point-at-eol)) :on nil :match 'font-lock-string-face) 63 | (error (format "Failed to find start string: %s" string))) 64 | (when (line-move 1 t) 65 | ;; comment line should not be highlighted by 'font-lock-string-face 66 | (assert-highlight-between 67 | (1+ (point-at-bol)) (1- (point-at-eol)) :on nil :match 'font-lock-comment-face))) 68 | 69 | (cl-defun expect-string (&key on search-strings) 70 | (prepare-file on) 71 | (cl-loop for s in search-strings do (check-highlight s))) 72 | 73 | ;; TODO: need document 74 | (cl-defun collect-char-points (&key on) 75 | (prepare-file on) 76 | (goto-char (point-min)) 77 | (search-forward "testCharacters: set[char] = {\n ") 78 | (let* ((limit (+ 128 22 128)) ; to prevent eternal loop 79 | char-points 80 | after-char-points 81 | checked-characters) 82 | (catch 'exit 83 | (while (and (not (eql 0 limit))) 84 | (setq limit (1- limit)) 85 | (re-search-forward 86 | (rx (group "'" (regex "[^']\\{1,4\\}") "'") 87 | (group (or (1+ "," (or blank "\n")) 88 | (and "\n" (* blank) "}")))) nil t) 89 | (let ((char (match-string 1)) 90 | (after-char (match-string 2))) 91 | (when char 92 | (let* ((start (- (point) (+ (length char) (length after-char)))) 93 | (end (+ start (1- (length char))))) 94 | (push (cons start end) char-points) 95 | (push (substring-no-properties char) checked-characters))) 96 | (when after-char 97 | (let* ((start2 (- (point) (length after-char))) 98 | (end2 (1- (point)))) 99 | (push (cons start2 end2) after-char-points))) 100 | (when (string-match "\\XFF" char) 101 | (throw 'exit nil))))) 102 | (cons char-points after-char-points) 103 | ;; You can check what you checked 104 | ;; (print (reverse checked-characters)) 105 | )) 106 | -------------------------------------------------------------------------------- /tests/indents/SMIE/&operater.nim: -------------------------------------------------------------------------------- 1 | proc testAnd = 2 | echo "foo" & 3 | "continuous &" & 4 | "after end of &" 5 | echo "dedent this line" 6 | 7 | proc testAndWithComment = 8 | echo "foo" & # comment 9 | "continuous &" & # comment 10 | "after end of &" # comment 11 | echo "dedent this line" # comment 12 | 13 | proc testAnd2 = 14 | echo "foo" & "bar" & "inside &" 15 | echo "foo" 16 | -------------------------------------------------------------------------------- /tests/indents/SMIE/README.md: -------------------------------------------------------------------------------- 1 | In this directory, all Nim's codes are unformatted and then 2 | tested by `indent-region` function. 3 | 4 | For example, if you have following code: 5 | 6 | 7 | ```nim 8 | if true: 9 | echo "hello" 10 | else true: 11 | echo "world" 12 | ``` 13 | 14 | is converted to 15 | 16 | 17 | ```nim 18 | if true: 19 | echo "hello" 20 | else true: 21 | echo "world" 22 | ``` 23 | 24 | So, you just need to place `expected` indentation codes in this 25 | directory here. 26 | 27 | Note that, the unformat function will erase even if it's string's 28 | spaces, in such case test that in ./smie/raw directory. 29 | -------------------------------------------------------------------------------- /tests/indents/SMIE/after-open-bracket.nim: -------------------------------------------------------------------------------- 1 | const 2 | testCharacters: set[char] = { 3 | ' ', '!', '"', '#', '$', '%', '&', '"', '(', ')', '*', '+', ',', '-', 4 | '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', 5 | } 6 | -------------------------------------------------------------------------------- /tests/indents/SMIE/after-open-paren.nim: -------------------------------------------------------------------------------- 1 | macro test(): stmt = 2 | result = newStmtList( 3 | newNimNode(nnkTypeSection).add( 4 | newNimNode(nnkTypeDef).add( 5 | ident("A"), 6 | newEmptyNode(), 7 | newNimNode(nnkEnumTy).add( 8 | newEmptyNode(), 9 | ident("a"), 10 | ident("b"))))) 11 | -------------------------------------------------------------------------------- /tests/indents/SMIE/after-open-slice.nim: -------------------------------------------------------------------------------- 1 | var 2 | a = [1, 2, 3, 4, 5, 6, 3 | 7, 8, 9, 10 4 | ] 5 | b = [ 6 | 1, 2, 3, 4, 5, 6, 7 | 7, 8, 9, 10 8 | ] 9 | c = [ 10 | 1, 2, 3, 4, 5, 6, 11 | 7, 8, 9, 10] 12 | d = "foo" 13 | -------------------------------------------------------------------------------- /tests/indents/SMIE/block-break.nim: -------------------------------------------------------------------------------- 1 | # Those tests are mainly checking after `break` 2 | proc testblock1 = 3 | block myblock1: 4 | echo("entering block") 5 | break 6 | echo "leave block" 7 | 8 | proc testBlock2() = 9 | block myBlockName: 10 | echo "foo" 11 | echo("bar") 12 | break myBlockName 13 | echo "foo" 14 | 15 | proc testBlock3() = 16 | block myblock2: 17 | echo("entering block") 18 | while true: 19 | echo("looping") 20 | break # leaves the loop, but not the block 21 | echo("still in block") 22 | break 23 | echo "exit from myblock2" 24 | 25 | proc breakTest() = 26 | block: 27 | if size == result.len: break 28 | let data = await socket.recv(size - result.len) 29 | if data == "": break # We've been disconnected. 30 | result.add data 31 | -------------------------------------------------------------------------------- /tests/indents/SMIE/case-stmt.nim: -------------------------------------------------------------------------------- 1 | var condition = true 2 | 3 | 4 | proc testCaseStmt () = 5 | case condition: 6 | of "with colon": 7 | echo "if you put ':' after CASE statement, " & 8 | "next OF operator should be indented." 9 | of "second OF": 10 | echo "hello" 11 | else: 12 | echo "case done" 13 | 14 | 15 | proc testCaseStmt2 () = 16 | case condition 17 | of "without colon": 18 | echo "if you don't put ':' after CASE statement, " & 19 | "next OF operator should not be indented." 20 | of "second OF": 21 | echo "hello" 22 | else: 23 | echo "case done" 24 | 25 | 26 | proc testCaseStmt3 () = 27 | case condition 28 | of "without colon": 29 | echo "if you don't put ':' after CASE statement, " & 30 | "next OF operator should not be indented." 31 | of "second OF": 32 | echo "hello" 33 | elif true: 34 | echo "also case statement can include ELIF" 35 | elif true: 36 | echo "also case statement can include ELIF" 37 | else: 38 | echo "case done" 39 | 40 | 41 | proc oneline_condition5() = 42 | case "a" 43 | of "b": echo "b" 44 | of "c": echo "c" 45 | of "d": echo "d" 46 | else: echo "else" 47 | 48 | 49 | proc oneline_condition6() = 50 | case "a": 51 | of "b": echo "b" 52 | of "c": echo "c" 53 | of "d": echo "d" 54 | else: echo "else" 55 | 56 | 57 | case x 58 | of true: 59 | let z = if y: 60 | echo "a" 61 | else: 62 | echo "b" 63 | else: 64 | echo "no" 65 | 66 | 67 | # check `var` and `else` 68 | var x = "foo" 69 | var a = case x: 70 | of "f": echo "f" 71 | of "fo": echo "fo" 72 | of "foo": echo "foo" 73 | else: "else" 74 | echo "check this line's indent" 75 | 76 | # check `let` and `else` 77 | let x = "bar" 78 | let a = case x 79 | of "f": echo "f" 80 | of "fo": echo "fo" 81 | of "foo": echo "foo" 82 | else: "else" 83 | echo "check this line's indent" 84 | -------------------------------------------------------------------------------- /tests/indents/SMIE/combined-control-stmt.nim: -------------------------------------------------------------------------------- 1 | var condition = true 2 | 3 | proc complex() = 4 | case condition: 5 | of "with colon": 6 | if true: 7 | echo "foo" 8 | else: echo "one line else" 9 | of "second OF": 10 | echo "hello" 11 | of "one line OF": echo "hello" 12 | else: 13 | echo "case done" 14 | when true: 15 | echo "hello" 16 | elif true: 17 | echo "make sure 'elif' is working" 18 | else: echo "one line else" 19 | if true: 20 | echo "after ELSE ended with single line" 21 | -------------------------------------------------------------------------------- /tests/indents/SMIE/comma.nim: -------------------------------------------------------------------------------- 1 | proc commaTest() = 2 | echo "hello", "world" 3 | echo "second line", "should not be indented" 4 | -------------------------------------------------------------------------------- /tests/indents/SMIE/comment.nim: -------------------------------------------------------------------------------- 1 | # comment test after empty line 2 | 3 | proc foo = 4 | # comment after `=` 5 | echo "check this line's indent" 6 | echo "hello" # comment 7 | # after previous line's comment 8 | 9 | var a:string = "a" 10 | # comment 11 | 12 | 13 | # After comment 14 | let 15 | a = 100 16 | b = 200 17 | 18 | 19 | # multiple comment lines 20 | # comment ... 21 | let 22 | c = 100 23 | d = 200 24 | 25 | 26 | type 27 | SomeSignedInt* = int|int8|int16|int32|int64 28 | ## docgen comment 29 | otherType = string # <- check this line's indent 30 | 31 | type 32 | RootEffect* {.compilerproc.} = object of RootObj ## \ 33 | ## comment after \\ 34 | -------------------------------------------------------------------------------- /tests/indents/SMIE/concept.nim: -------------------------------------------------------------------------------- 1 | # one argument 2 | type 3 | Node* = concept n 4 | `==`(n, n) is bool 5 | 6 | 7 | # two argument 8 | type Monoid = concept x, y 9 | x + y is type(x) 10 | type(z(type(x))) is type(x) 11 | 12 | 13 | # with `var` 14 | type RNG* = concept var rng 15 | rng.randomUint32() is uint32 16 | -------------------------------------------------------------------------------- /tests/indents/SMIE/const.nim: -------------------------------------------------------------------------------- 1 | # single line 2 | const foo: string = "string" 3 | const bar: string = "string" 4 | 5 | const 6 | foo1: string = "string" 7 | foo2 = "foo" 8 | bar1: string = "string" 9 | bar2 = "bar" 10 | 11 | 12 | # insert newline after equal 13 | const 14 | foo3: string = 15 | "string" 16 | foo4 = 17 | "foo" 18 | 19 | 20 | # with pth/ref 21 | when (T is ref): 22 | const r: ref T 23 | echo "indent test" 24 | else: 25 | const p: ptr T 26 | echo "indent test" 27 | 28 | 29 | # only type 30 | when nimvm: 31 | const r: T 32 | echo "indent test" 33 | else: 34 | const p: T 35 | echo "indent test" 36 | -------------------------------------------------------------------------------- /tests/indents/SMIE/converter.nim: -------------------------------------------------------------------------------- 1 | converter getSocket*(s: AsyncSocket): Socket = 2 | return s.socket 3 | -------------------------------------------------------------------------------- /tests/indents/SMIE/defer-with-comment.nim: -------------------------------------------------------------------------------- 1 | proc testDefer2 = 2 | defer: # comment's colon : 3 | echo("should indent above line has comment") 4 | -------------------------------------------------------------------------------- /tests/indents/SMIE/defer.nim: -------------------------------------------------------------------------------- 1 | proc testDefer = 2 | defer: 3 | echo("should indent after colon") 4 | -------------------------------------------------------------------------------- /tests/indents/SMIE/do_notation.nim: -------------------------------------------------------------------------------- 1 | proc DoNotation = 2 | cities = cities.map do (x:string) -> string: 3 | "City of " & x 4 | -------------------------------------------------------------------------------- /tests/indents/SMIE/empty_line.nim: -------------------------------------------------------------------------------- 1 | proc test_empty_line = 2 | if true: 3 | echo "foo" 4 | 5 | when true: 6 | echo "foo" 7 | -------------------------------------------------------------------------------- /tests/indents/SMIE/enum.nim: -------------------------------------------------------------------------------- 1 | type 2 | Direction = enum 3 | north, east, 4 | south, west 5 | 6 | 7 | type 8 | TokenType = enum 9 | a = 2, 10 | b = 4, 11 | c = 89 12 | 13 | 14 | type # with pragma 15 | MyEnum {.pure.} = enum 16 | valueA, valueB, 17 | valueC, valueD 18 | -------------------------------------------------------------------------------- /tests/indents/SMIE/for-break.nim: -------------------------------------------------------------------------------- 1 | proc testFor = 2 | for x, y in foo: 3 | echo "process" 4 | if true: 5 | echo "break keep indent" 6 | break 7 | else: 8 | echo "foo" 9 | -------------------------------------------------------------------------------- /tests/indents/SMIE/forward_declaration.nim: -------------------------------------------------------------------------------- 1 | proc foo(a, b: int, c, d: bool): int 2 | proc bar(a, b: int, c, d: bool): tuple[a: int] 3 | proc buzz(a, b: int, c, d: bool): int 4 | -------------------------------------------------------------------------------- /tests/indents/SMIE/forward_declaration2.nim: -------------------------------------------------------------------------------- 1 | # Test forward declaration after pragma 2 | proc testForwardDeclaration() = 3 | proc getOccupiedMem*(): int {.rtl.} 4 | proc getFreeMem*(): int {.rtl.} 5 | proc getOccupiedSharedMem*(): int {.rtl.} 6 | echo("check after forward declaration") 7 | -------------------------------------------------------------------------------- /tests/indents/SMIE/if-stmt.nim: -------------------------------------------------------------------------------- 1 | var condition = true 2 | 3 | proc testIfStmt () = 4 | if condition: 5 | echo "hello" 6 | elif false: 7 | echo "hello" 8 | elif false: 9 | echo "second elif" 10 | else: 11 | echo "hello" 12 | 13 | 14 | if nimvm: 15 | proc abs*(x: int64): int64 = 16 | if x < 0: -x else: x 17 | else: # <- check this line is dedented correctly 18 | proc abs*(x: int64): int64 = 19 | echo "foo" 20 | 21 | 22 | let x = if true: 23 | echo "foo" 24 | elif true: 25 | echo "bar" 26 | else: 27 | echo "buzz" 28 | 29 | 30 | var y = if true: 31 | echo "foo" 32 | elif true: 33 | echo "bar" 34 | else: 35 | echo "buzz" 36 | 37 | 38 | # check indent including `in` 39 | if s.kind in {skResult, skTemp}: 40 | echo "foo" 41 | 42 | if true == 1 in (1..10): 43 | echo "bar" 44 | 45 | 46 | # check `var` and `else` 47 | var a = if true: "a" 48 | else: "b" 49 | echo "check this line's indent" 50 | 51 | # check `let` and `else` 52 | let b = if true: "a" 53 | else: "b" 54 | echo "check this line's indent" 55 | -------------------------------------------------------------------------------- /tests/indents/SMIE/import.nim: -------------------------------------------------------------------------------- 1 | import 2 | parseutils, strutils, parseopt, parsecfg, strtabs, unicode, pegs, ropes, 3 | os, osproc, times 4 | 5 | 6 | # new syntax from version 0.16.0 7 | import compiler / [ast, 8 | parser, 9 | lexer] 10 | 11 | import compiler / [ 12 | bitsets, 13 | cgen, 14 | depends 15 | ] 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/indents/SMIE/infix_colon.nim: -------------------------------------------------------------------------------- 1 | # smie only care about `smie-closer-alist`s tokens. So, in following 2 | # situation, indent of line 9 (Check this...) referred ":" below 3 | # "object" before I fixed the indent. 4 | type 5 | A = object 6 | str: string 7 | 8 | 9 | echo "foo" "bar" 10 | echo "Check this line's indent" 11 | -------------------------------------------------------------------------------- /tests/indents/SMIE/iter.nim: -------------------------------------------------------------------------------- 1 | proc testFor = 2 | for x, y in foo: 3 | echo "hello" 4 | -------------------------------------------------------------------------------- /tests/indents/SMIE/iterator.nim: -------------------------------------------------------------------------------- 1 | iterator a1(ticker: int) {.closure.} = 2 | echo "a1: A" 3 | yield 4 | echo "a1: B" 5 | yield 6 | echo "a1: C" 7 | yield 8 | echo "a1: D" 9 | -------------------------------------------------------------------------------- /tests/indents/SMIE/iterator2.nim: -------------------------------------------------------------------------------- 1 | proc mycount(a, b: int): iterator (): int = 2 | result = iterator (): int = 3 | var x = a 4 | while x <= b: 5 | yield x 6 | inc x 7 | break 8 | -------------------------------------------------------------------------------- /tests/indents/SMIE/let.nim: -------------------------------------------------------------------------------- 1 | # single line 2 | let foo: string = "string" 3 | let bar: string = "string" 4 | 5 | let 6 | foo1: string = "string" 7 | foo2 = "foo" 8 | bar1: string = "string" 9 | bar2 = "bar" 10 | 11 | 12 | # insert newline after equal 13 | let 14 | foo3: string = 15 | "string" 16 | foo4 = 17 | "foo" 18 | 19 | 20 | # with pth/ref 21 | when (T is ref): 22 | let r: ref T 23 | echo "indent test" 24 | else: 25 | let p: ptr T 26 | echo "indent test" 27 | 28 | 29 | # only type 30 | when nimvm: 31 | let r: T 32 | echo "indent test" 33 | else: 34 | let p: T 35 | echo "indent test" 36 | -------------------------------------------------------------------------------- /tests/indents/SMIE/long-condition.nim: -------------------------------------------------------------------------------- 1 | if true or false and true or false 2 | true or false and true or false: 3 | echo "long condition lines should be indented" 4 | elif true or false and true or false 5 | true or false and true or false: 6 | echo "long condition lines should be indented" 7 | else: 8 | echo "foo" 9 | -------------------------------------------------------------------------------- /tests/indents/SMIE/macro.nim: -------------------------------------------------------------------------------- 1 | macro `[]`*[A, B](t: var Table[A, B], key: A): var B = 2 | let opt = getImpl(t, key) 3 | foo 4 | 5 | proc testMacro = 6 | dumpTree: 7 | foo: 8 | x = "bar" 9 | 10 | 11 | proc testMacro = 12 | fakeFunc: echo "foo" 13 | echo "check this line" 14 | -------------------------------------------------------------------------------- /tests/indents/SMIE/method.nim: -------------------------------------------------------------------------------- 1 | method collide(a, b: Thing) {.inline.} = 2 | quit "to override!" 3 | 4 | method collide(a: Thing, b: Unit) {.inline.} = 5 | echo "1" 6 | 7 | method collide(a: Unit, b: Thing) {.inline.} = 8 | echo "2" 9 | -------------------------------------------------------------------------------- /tests/indents/SMIE/nimble_file.nim: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.0" 4 | author = "Jhon Do" 5 | description = "Test" 6 | license = "XXX" 7 | 8 | # Dependencies 9 | 10 | requires "nim >= 0.12.1" 11 | 12 | task tests, "test next line's indent": 13 | exec "this line should be indented correctly" 14 | -------------------------------------------------------------------------------- /tests/indents/SMIE/object-of.nim: -------------------------------------------------------------------------------- 1 | type 2 | Unit = object of Thing 3 | x: int 4 | y: int 5 | NewType* = object of RootEffect # check this line is dedented 6 | 7 | type 8 | A = 9 | object of RootRef 10 | B[T] = 11 | object of WrappedItem 12 | value: T 13 | 14 | 15 | # with ref 16 | type 17 | C = 18 | ref object of RootRef 19 | D[T] = 20 | ref object of WrappedItem 21 | value: T 22 | 23 | 24 | type 25 | Node = ref NodeObj 26 | NodeObj {.acyclic, final.} = object 27 | left, right: Node 28 | data: string 29 | height: int 30 | weight: int 31 | NewTyp = string 32 | 33 | type 34 | RootEffect* {.compilerproc.} = object of RootObj 35 | TimeEffect* = object of RootEffect ## Time effect. 36 | IOEffect* = object of RootEffect ## IO effect. 37 | ReadIOEffect* = object of IOEffect ## Effect describing a read IO operation. 38 | WriteIOEffect* = object of IOEffect ## Effect describing a write IO operation. 39 | ExecIOEffect* = object of IOEffect ## Effect describing an executing IO operation. 40 | Exception* {.compilerproc.} = object of RootObj 41 | trace: string 42 | SystemError* = object of Exception 43 | IOError* = object of SystemError 44 | -------------------------------------------------------------------------------- /tests/indents/SMIE/object.nim: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nim-lang/nim-mode/625cc023bd75a741b7d4e629e5bec3a52f45b4be/tests/indents/SMIE/object.nim -------------------------------------------------------------------------------- /tests/indents/SMIE/oneline_conditions.nim: -------------------------------------------------------------------------------- 1 | proc oneline_condition1() = 2 | if true: echo "foo" 3 | echo "foo" 4 | 5 | 6 | proc oneline_condition2() = 7 | when true: echo "foo" 8 | echo "foo" 9 | 10 | 11 | proc oneline_condition3() = 12 | if true: 13 | echo "foo" 14 | elif true: echo "foo" 15 | echo "foo" 16 | 17 | 18 | proc oneline_condition4() = 19 | if true: echo "foo" 20 | else: echo "foo" 21 | echo "foo" 22 | 23 | 24 | proc oneline_condition5() = 25 | case "a" 26 | of "b": echo "b" 27 | echo "foo" 28 | -------------------------------------------------------------------------------- /tests/indents/SMIE/paren-space.nim: -------------------------------------------------------------------------------- 1 | proc parenNoSpace(n: int, 2 | f: float, 3 | str: string) = 4 | echo "0" 5 | 6 | 7 | proc parenOneSpace( n: int, 8 | f: float, 9 | str: string ) = 10 | echo "1" 11 | 12 | proc parenTwoSpaces( n: int, 13 | f: float, 14 | str: string ) = 15 | echo "2" 16 | -------------------------------------------------------------------------------- /tests/indents/SMIE/proc.nim: -------------------------------------------------------------------------------- 1 | # indent between `proc` and `=` 2 | proc test1[T](a: var ref T, finalizer: proc (x: ref T) {.nimcall.}) 3 | {.magic: "NewFinalize", noSideEffect.} = 4 | echo "hello" 5 | 6 | 7 | # If proc's return type is `var` 8 | proc `[]`*[A, B](t: var Table[A, B], key: A): var B = 9 | let opt = getImpl(t, key) 10 | 11 | 12 | # Check indentation if it's including `var' in the args. 13 | # Second line should indents until "(" 14 | proc testVarAsArgs(n: PNode, marker: var IntSet, 15 | indent, maxRecDepth: int): Rope 16 | 17 | 18 | # check after {.'s indent 19 | proc test3[T](a: var ref T, finalizer: proc (x: ref T) {.nimcall.}) {. 20 | magic: "NewFinalize", noSideEffect.} = 21 | echo "hello" 22 | 23 | proc `[]`*[I: Ordinal;T](a: T; i: I): T {. 24 | noSideEffect, magic: "ArrGet".} 25 | 26 | # check indent after proc signature 27 | type 28 | emacs_finalizer_function* {.importc: "emacs_finalizer_function", 29 | header: "".} = proc(void: pointer) 30 | emacs_value* {.importc: "struct emacs_value_tag", header: "".} = 31 | pointer 32 | other_type = string 33 | 34 | 35 | # ignore quoted token 36 | proc `type`*(x: expr): typeDesc {.magic: "TypeOf", noSideEffect, compileTime.} = 37 | discard 38 | -------------------------------------------------------------------------------- /tests/indents/SMIE/proc_as_type.nim: -------------------------------------------------------------------------------- 1 | type 2 | emacs_env* {.importc: "struct emacs_env_25", header: "".} = object 3 | vec_size*: proc(env: ptr emacs_env, vec: emacs_value): ptrdiff_t 4 | otherObj : string 5 | 6 | 7 | type 8 | Callback = proc (s: string):string {.raises: [IOError].} 9 | otherType = int 10 | -------------------------------------------------------------------------------- /tests/indents/SMIE/raw/inside_string.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | <- Check this space remains or not 3 | <- Check this space remains or not 4 | <- Check this space remains or not 5 | """ 6 | -------------------------------------------------------------------------------- /tests/indents/SMIE/semicolon.nim: -------------------------------------------------------------------------------- 1 | # Inside args' ";" treated as "," 2 | proc hello(a: string, b: string; 3 | c: string) = 4 | echo a b c 5 | -------------------------------------------------------------------------------- /tests/indents/SMIE/static.nim: -------------------------------------------------------------------------------- 1 | # static statement 2 | static: 3 | echo "should indent after colon" 4 | -------------------------------------------------------------------------------- /tests/indents/SMIE/string.nim: -------------------------------------------------------------------------------- 1 | proc testString = 2 | discard """End of colon should be ignored : 3 | This line should not indented. 4 | """ 5 | -------------------------------------------------------------------------------- /tests/indents/SMIE/template.nim: -------------------------------------------------------------------------------- 1 | template `[]`*[A, B](t: var Table[A, B], key: A): var B = 2 | let opt = getImpl(t, key) 3 | foo 4 | -------------------------------------------------------------------------------- /tests/indents/SMIE/try-stmt.nim: -------------------------------------------------------------------------------- 1 | proc testTryStmt() = 2 | var f: File 3 | if open(f, "file"): 4 | try: 5 | var a = readLine(f) 6 | echo("num:" & $(parseInt(a))) 7 | except OverflowError: 8 | echo("overflow") 9 | except Others: 10 | echo("something") 11 | finally: 12 | close(f) 13 | 14 | 15 | proc testTry2() = 16 | let x = try: 17 | parseInt("133a") 18 | except: 19 | -1 20 | finally: 21 | echo "hi" 22 | -------------------------------------------------------------------------------- /tests/indents/SMIE/tuple.nim: -------------------------------------------------------------------------------- 1 | type 2 | Person = tuple # type representing a person 3 | name: string # a person consists of a name 4 | age: int 5 | weight: int 6 | 7 | 8 | type 9 | Person2 = tuple[name: string, age: int] 10 | afterTuple = string 11 | 12 | 13 | type 14 | Person3 = tuple[ 15 | name: string, 16 | age: int 17 | ] 18 | afterTuple2 = string 19 | 20 | 21 | # tuple with `var` 22 | var building: tuple[street: string, number: int] = ( 23 | "Rue del Percebe", 24 | 13 25 | ) 26 | 27 | # check return type's tuple 28 | proc tupleTest*[T](): tuple[key:int, val:var T] = 29 | echo "this line should be indented correctly" 30 | -------------------------------------------------------------------------------- /tests/indents/SMIE/type.nim: -------------------------------------------------------------------------------- 1 | type 2 | intmax_t* {.importc: "intmax_t", header: "".} = 3 | clonglong 4 | emacs_finalizer_function* {.importc: "emacs_finalizer_function", 5 | header: "".} = proc(void: pointer) 6 | emacs_value* {.importc: "struct emacs_value_tag", 7 | header: "".} = pointer 8 | ptrdiff_t* {.importc: "ptrdiff_t", header: "".} = int 9 | emacs_env* {.importc: "struct emacs_env_25", 10 | header: "".} = object 11 | size: ptrdiff_t 12 | make_global_ref*: proc(env: ptr emacs_env, any_reference: emacs_value): emacs_value 13 | free_global_ref*: proc(env: ptr emacs_env, global_reference: emacs_value): pointer 14 | 15 | 16 | type 17 | RootObj* {.exportc: "TNimObject", inheritable.} = 18 | object 19 | s: string # this line should be dedented 20 | 21 | 22 | type 23 | A* {.exportc: "TNimObject", inheritable.} = 24 | object 25 | B* = ref RootObj # check this line's indent 26 | C* = 27 | object # check if there is other "object" 28 | 29 | 30 | # check indent after pragma 31 | type 32 | typeA* = proc(): cint {.cdecl.} 33 | typeB* = proc(): cint {.cdecl.} 34 | typeC* {.importc: "struct emacs_runtime", 35 | header: "".} = object 36 | size: ptrdiff_t 37 | soemthing: proc(): ptr foo {.cdecl.} 38 | # check this line's indent 39 | -------------------------------------------------------------------------------- /tests/indents/SMIE/using.nim: -------------------------------------------------------------------------------- 1 | # indentation for `using` 2 | 3 | {.experimental.} 4 | using 5 | p: pointer 6 | c: char 7 | 8 | when true: 9 | using 10 | s: string 11 | i: int 12 | -------------------------------------------------------------------------------- /tests/indents/SMIE/var.nim: -------------------------------------------------------------------------------- 1 | # single line 2 | var foo: string = "string" 3 | var bar: string = "string" 4 | 5 | var 6 | foo1: string = "string" 7 | foo2 = "foo" 8 | bar1: string = "string" 9 | bar2 = "bar" 10 | 11 | 12 | # insert newline after equal 13 | var 14 | foo3: string = 15 | "string" 16 | foo4 = 17 | "foo" 18 | 19 | 20 | # with pth/ref 21 | when (T is ref): 22 | var r: ref T 23 | echo "indent test" 24 | else: 25 | var p: ptr T 26 | echo "indent test" 27 | 28 | 29 | # only type 30 | when nimvm: 31 | var r: T 32 | echo "indent test" 33 | else: 34 | var p: T 35 | echo "indent test" 36 | -------------------------------------------------------------------------------- /tests/indents/SMIE/when-stmt.nim: -------------------------------------------------------------------------------- 1 | var condition = true 2 | 3 | proc testWhenStmt () = 4 | when condition: 5 | echo "hello" 6 | elif false: 7 | echo "hello" 8 | elif false: 9 | echo "second elif" 10 | else: 11 | echo "hello" 12 | 13 | 14 | let x = when true: 15 | echo "foo" 16 | elif true: 17 | echo "bar" 18 | else: 19 | echo "buzz" 20 | 21 | 22 | var y = when true: 23 | echo "foo" 24 | elif true: 25 | echo "bar" 26 | else: 27 | echo "buzz" 28 | 29 | 30 | # check `var` and `else` 31 | var a = when true: "a" 32 | else: "b" 33 | echo "check this line's indent" 34 | 35 | # check `let` and `else` 36 | let b = when true: "a" 37 | else: "b" 38 | echo "check this line's indent" 39 | -------------------------------------------------------------------------------- /tests/indents/SMIE/while-stmt.nim: -------------------------------------------------------------------------------- 1 | var condition = true 2 | 3 | proc testWhile () = 4 | while condition: 5 | condition = false 6 | break 7 | echo "indent end with break" 8 | 9 | 10 | proc testWhile2 = 11 | while true: 12 | var x = nil 13 | if x.isNil: break 14 | echo "should be dedented after break" 15 | 16 | 17 | proc testWhile3 = 18 | while true: 19 | echo "process" 20 | if true: 21 | echo "something" 22 | break 23 | echo "after break should be dedented" 24 | -------------------------------------------------------------------------------- /tests/indents/SMIE/with-variable/break.nim: -------------------------------------------------------------------------------- 1 | # check after break's behavior with setting variable: 2 | # (setq nim-smie-dedent-after-break '("if" "when" "elif" "else" "finally")) 3 | proc breakTest() = 4 | block: 5 | if true: echo "else break" 6 | else: break 7 | echo "`else` with `break` should be dedented" 8 | 9 | proc breakTest2() = 10 | while true: 11 | if true: echo "else break" 12 | elif true: break 13 | echo "`else` with `break` should be dedented" 14 | 15 | proc testFor = 16 | for x, y in foo: 17 | if x == true: break 18 | else: break 19 | echo "after else break should be dedented" 20 | 21 | proc testTryWithBreak() = 22 | block: 23 | try: parseInt("133a") 24 | except: -1 25 | finally: break 26 | echo "after `finally: break` should be dedented" 27 | -------------------------------------------------------------------------------- /tests/syntax/backtick.nim: -------------------------------------------------------------------------------- 1 | # test backtick highlight 2 | # Note that for performance reason, this highlight's regex is picky. 3 | 4 | # `single backticks` and ``double backticks`` 5 | 6 | ## `single backticks` and ``double backticks`` 7 | 8 | #[ `single backticks` and ``double backticks`` ]# 9 | 10 | ##[ `single backticks` and ``double backticks`` ]## 11 | 12 | #[ start with line start 13 | `single backticks` 14 | ``double backticks`` 15 | ]# 16 | -------------------------------------------------------------------------------- /tests/syntax/char.nim: -------------------------------------------------------------------------------- 1 | # referred http://nim-lang.org/docs/manual.html#lexical-analysis-character-literals 2 | const 3 | testCharacters: set[char] = { 4 | ' ', '!', '"', '#', '$', '%', '&', '"', '(', ')', '*', '+', ',', '-', 5 | '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', 6 | ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 7 | 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 8 | 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 9 | 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 10 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', 11 | '|', '}', '~', '\r', '\c', '\l', '\f', '\t', '\v', '\"', '\'', 12 | '\0', '\1', '\2', '\3', '\4', '\5', '\6', '\7', '\8', '\9', 13 | '\a', '\b', '\c', '\e', '\x10', '\xaf', '\xff', '\x80', '\xFF' 14 | } 15 | -------------------------------------------------------------------------------- /tests/syntax/docstring.nim: -------------------------------------------------------------------------------- 1 | # This test checks syntax highlight of docstring (##) 2 | 3 | ## Top level docstring should be highlighted 4 | 5 | proc testInsideProc() = 6 | ## inside proc's docstring should be highlighted 7 | echo("foo") 8 | 9 | -------------------------------------------------------------------------------- /tests/syntax/export.nim: -------------------------------------------------------------------------------- 1 | var single_line*:string = "foo" 2 | 3 | let 4 | multi_line*:string = "foo" 5 | multi_line2*:string = "foo" 6 | 7 | proc foo*() = echo "foo" 8 | -------------------------------------------------------------------------------- /tests/syntax/format.nim: -------------------------------------------------------------------------------- 1 | # highlight $# or $[1-9][0-9] inside string for `format` 2 | import strutils 3 | 4 | echo """ 5 | $1 + $2 = $# 6 | """.format("1", "2", "3") 7 | -------------------------------------------------------------------------------- /tests/syntax/function_name.nim: -------------------------------------------------------------------------------- 1 | proc foo(): string = 2 | "string" 3 | -------------------------------------------------------------------------------- /tests/syntax/multiline_comment.nim: -------------------------------------------------------------------------------- 1 | ###[ <- this is treated as single comment 2 | echo "#[]# is multi line comment" 3 | #[ 4 | bla bla bla bla bla bla 5 | # inside hash 6 | #[nesting comment]# 7 | ##[nesting comment]## 8 | bla bla bla bla bla bla 9 | ]# 10 | echo "##[]## is multi line documentation comment" 11 | ##[ 12 | multi line documentation comment can include 13 | #[nesting]# and #[nesting] (unbalance #[ is ok) 14 | 15 | Also nested docummentation comment: ##[ nested doc comment ]## 16 | (, but need to provide both open ##[ and close ]## ) 17 | ]## 18 | 19 | proc testMultiComment = 20 | ##[ multi line comment test 21 | aaa #[nesting]# bbb 22 | ]## 23 | echo "foo bar" 24 | 25 | var foo #[ comment ]#: string = "foo" 26 | 27 | # boundary 28 | echo "#["# foo 29 | echo "]#"# bar 30 | echo "##["# foo 31 | echo "]##"# bar 32 | echo "check ->"#" <- double quote 33 | -------------------------------------------------------------------------------- /tests/syntax/number.nim: -------------------------------------------------------------------------------- 1 | var 2 | unsignedInt = 100'u 3 | i8 = 8'i8 4 | i16 = 16'i16 5 | i32 = 32'i32 6 | i64 = 64'i64 7 | f32 = 3.14'f32 8 | f64 = 3.14'f64 9 | 10 | -------------------------------------------------------------------------------- /tests/syntax/pragma.nim: -------------------------------------------------------------------------------- 1 | # pragma without end of "." 2 | {.deprecated: [TMimeDB: MimeDB]} 3 | # with strings 4 | {.warning: "Profiling support is turned off!".} 5 | -------------------------------------------------------------------------------- /tests/syntax/single_comment.nims: -------------------------------------------------------------------------------- 1 | # I really don't know why only .nims file can not highlight correctly. 2 | 3 | when true: 4 | echo "foo" 5 | -------------------------------------------------------------------------------- /tests/syntax/string.nim: -------------------------------------------------------------------------------- 1 | let s = "foobar" 2 | 3 | var heredoc = """'one' ''two'' 4 | '''three''' ""two double quotes"" 5 | """ 6 | 7 | var endOfQuote = "foo'" 8 | # this line should be comment face 9 | var endOfQuote2 = """foo'""" 10 | # this line should be comment face 11 | 12 | var beginningOfQuote = "'foo" 13 | # this line should be comment face 14 | var beginningOfQuote2 = """'foo""" 15 | # this line should be comment face 16 | 17 | var enclosedQuote = "'foo'" 18 | # this line should be comment face 19 | var enclosedQuote2 = """'foo'""" 20 | # this line should be comment face 21 | 22 | var escapedSingleQuote = "foo\'" 23 | # this line should be comment face 24 | var escapedSingleQuote2 = """foo\'""" 25 | # this line should be comment face 26 | 27 | var escapedDoubleQuote = "foo\"" 28 | # this line should be comment face 29 | var escapedDoubleQuote2 = """foo\"""" 30 | # this line should be comment face 31 | 32 | var unbalancedDoubleQuote = """8 double quotes->"""""""" 33 | # this line should be comment face 34 | var unbalancedDoubleQuote2 = """""<-5 double quotes 11 double quotes->""""""""""" 35 | # this line should be comment face 36 | var unbalancedDoubleQuote3 = """"<-4 double quotes""" 37 | # this line should be comment face 38 | var unbalancedDoubleQuote4 = """"enclosed double quotes"""" 39 | # this line should be comment face 40 | 41 | var rawString = r"foo""bar""buzz" 42 | # this line should be comment face 43 | var rawStringIssue210 = r"" 44 | # this line should be comment face 45 | 46 | #[ #212 raw string literal with end of `\` ]# 47 | var rawStringWithBackslash = r"\" 48 | # this line should be comment face 49 | 50 | var rawStringWithBackslash2 = R2D2"\" 51 | # should be fontified even if identifier has number 52 | 53 | var rawStringWithBackslash3 = sql"\" 54 | # this line should be comment face 55 | 56 | var rawStringWithBackslash4 = """abc\""" 57 | # this line should be comment face 58 | 59 | var rawStringWithBackslash5 = r"a\""bc\" 60 | # this line should be comment face 61 | 62 | var rawStringWithBackslash6 = sql"""abc\""" 63 | # this line should be comment face 64 | -------------------------------------------------------------------------------- /tests/syntax/test_is_and_distinct.nim: -------------------------------------------------------------------------------- 1 | # test highlighting a word as type face after `is` and `distinct` 2 | 3 | type 4 | custom_type = tuple[a: string] 5 | custom_type2 = distinct custom_type # <- highlight 6 | 7 | var it: custom_type = (a: "foo") 8 | if it is custom_type: 9 | echo "custom_type should be highlighted as type face" 10 | elif it is custom_type2: 11 | echo "custom_type2 should be highlighted as type face" 12 | -------------------------------------------------------------------------------- /tests/syntax/test_nimscript.nims: -------------------------------------------------------------------------------- 1 | # Test highlight for NimScript files 2 | mode = ScriptMode.Verbose 3 | 4 | when true: 5 | task build, "<- check highlight": 6 | echo "foo" 7 | elif true: 8 | task tests, "<- check highlight": 9 | echo "foo" 10 | elif true: 11 | task bench, "<- check highlight": 12 | echo "foo" 13 | else: 14 | exec "echo foo" 15 | -------------------------------------------------------------------------------- /tests/syntax/varargs.nim: -------------------------------------------------------------------------------- 1 | # check highlight of `varargs` 2 | proc testVarargs[T](x: varargs[T]): T = 3 | result = x[0] 4 | for i in 1..high(x): 5 | if x[i] < result: result = x[i] 6 | -------------------------------------------------------------------------------- /tests/test-smie-indent.el: -------------------------------------------------------------------------------- 1 | ;;; test-smie-indent.el --- Tests indentation using SMIE -*-lexical-binding:t-*- 2 | 3 | ;;; Code: 4 | 5 | (require 'nim-mode) 6 | (require 'cl-lib) 7 | 8 | ;; SMIE indentation supports from Emacs 24.4 9 | (when (version<= "24.4" emacs-version) 10 | (defvar test-dir "tests/indents/SMIE/") 11 | (defvar temp-file "/tmp/nim-test.nim") 12 | 13 | (defun SMIE-indent-and-compare (file &optional raw) 14 | (if (not (eq (point-min) (point-max))) 15 | (error "Buffer isn't erased") 16 | (it (format "should indent %s correctly" (file-name-base file)) 17 | (with-temp-file temp-file 18 | (nim-smie-test-insert-and-indent file raw)) 19 | (expect 20 | (shell-command-to-string 21 | (format "diff %s %s" temp-file (shell-quote-argument file))) 22 | :to-equal "")))) 23 | 24 | (defun nim-smie-test-insert-and-indent (&optional file raw) 25 | (interactive) 26 | (erase-buffer) 27 | (nim-mode) 28 | (insert-file-contents-literally 29 | (or file (read-from-minibuffer "file :" (buffer-file-name)))) 30 | (unless raw (nim-unformat-buffer)) 31 | (set-mark (point-min)) 32 | (goto-char (point-max)) 33 | (call-interactively 'indent-region)) 34 | 35 | ;; just for manually check 36 | ;; (global-set-key (kbd "C-0") 'nim-smie-test-insert-and-indent) 37 | 38 | (defun nim-unformat-buffer () 39 | "Convenience function to make a no indented source file." 40 | (interactive) 41 | ;; implemented for non-interactive use, but for debug purpose, 42 | ;; I added that interactive. 43 | (let ((message-log-max nil)) 44 | (while (re-search-forward "^ +" nil t) 45 | (replace-match "" nil nil)))) 46 | 47 | (defun nim-indent-test (dir &optional raw) 48 | (cl-loop for file in (directory-files dir) 49 | for f = (format "%s%s" dir file) 50 | if (and (not (member file '("." ".."))) 51 | (equal "nim" (file-name-extension file))) 52 | do (SMIE-indent-and-compare f raw))) 53 | 54 | (defadvice make-progress-reporter (around prevent-echoing activate) 55 | "only replace indent region’s echo message" 56 | (if (equal (ad-get-arg 0) "Indenting region...") 57 | (ad-set-arg 0 "...")) 58 | ad-do-it) 59 | 60 | (defadvice progress-reporter-done (around prevent-echoing activate) 61 | "Prevent done message during test." 62 | "") 63 | 64 | (describe 65 | "Indentation" 66 | (after-each (kill-this-buffer)) 67 | (nim-indent-test test-dir)) 68 | 69 | ;; This test don’t unformat. 70 | ;; I intended this to test indentation inside string 71 | (describe 72 | "Indentation (raw)" 73 | (after-each (kill-this-buffer)) 74 | (nim-indent-test (format "%s/raw/" test-dir) t)) 75 | 76 | (describe 77 | "indentation (after break)" 78 | (before-each 79 | (setq nim-smie-dedent-after-break '("if" "when" "elif" "else" "finally"))) 80 | (after-each 81 | (kill-this-buffer) 82 | (setq nim-smie-dedent-after-break nil)) 83 | (nim-indent-test (format "%s/with-variable/" test-dir) t)) 84 | ) ; keep this here for less changes 85 | 86 | ;; Local Variables: 87 | ;; no-byte-compile: t 88 | ;; End: 89 | 90 | ;;; test-smie-indent.el ends here 91 | -------------------------------------------------------------------------------- /tests/test-syntax.el: -------------------------------------------------------------------------------- 1 | ;;; test-syntax.el --- Tests for syntax.el -*-lexical-binding:t-*- 2 | 3 | ;;; Code: 4 | 5 | (load (concat default-directory "tests/helper/test-syntax-helper.el")) 6 | 7 | (describe 8 | "Testing Syntax Highlight" 9 | 10 | (before-each 11 | (set-buffer (get-buffer-create "*Test*")) 12 | (erase-buffer) 13 | ;; may need to turn off this to adapt triple double quotes. 14 | (prettify-symbols-mode 0) 15 | (nim-mode)) 16 | 17 | (after-each 18 | (kill-buffer (get-buffer-create "*Test*"))) 19 | 20 | (it "should highlight with strings" 21 | (expect (face-at-point-of 10 :on "string.nim") 22 | :to-be font-lock-string-face) 23 | (expect (face-at-point-of 5 :on "string.nim") 24 | :to-be font-lock-variable-name-face)) 25 | 26 | (it "should highlight inside here document" 27 | (assert-highlight-between 33 86 :on "string.nim" 28 | :match font-lock-string-face)) 29 | 30 | (it "should highlight export variables" 31 | (assert-highlights-between 32 | '((5 . 16) (40 . 50) (69 . 80) (103 . 106)) 33 | :on "export.nim" :match 'nim-font-lock-export-face)) 34 | 35 | (it "should highlight function name" 36 | (assert-highlight-between 6 8 :on "function_name.nim" 37 | :match font-lock-function-name-face)) 38 | 39 | (it "should highlight double quoted string with single quotes" 40 | ;; This test makes sure whether highlights after "xxx = " are 41 | ;; font-lock-string-face and next lines are not affected by the string 42 | ;; face. 43 | (expect-string 44 | :on "string.nim" 45 | :search-strings 46 | '("var endOfQuote = " "var endOfQuote2 = " 47 | "var beginningOfQuote = " "var beginningOfQuote2 = " 48 | "var enclosedQuote = " "var enclosedQuote2 = " 49 | "var escapedSingleQuote = " "var escapedSingleQuote2 = " 50 | "var escapedDoubleQuote = " "var escapedDoubleQuote2 = " 51 | "var unbalancedDoubleQuote = " "var unbalancedDoubleQuote2 = " 52 | "var unbalancedDoubleQuote3 = " "var unbalancedDoubleQuote4 = " 53 | "var rawString = r" "var rawStringIssue210 = r" 54 | ;; #212 55 | "var rawStringWithBackslash = r" 56 | "var rawStringWithBackslash2 = R2D2" 57 | "var rawStringWithBackslash3 = sql" 58 | "var rawStringWithBackslash4 = " 59 | "var rawStringWithBackslash5 = r" 60 | "var rawStringWithBackslash6 = sql"))) 61 | 62 | (it "should highlight numbers" 63 | (assert-highlights-between 64 | '(;; number part 65 | (21 . 23) (43 . 43) (64 . 65) 66 | (87 . 88) (110 . 111) (133 . 133) (158 . 158) 67 | ;; type part 68 | (24 . 25) (44 . 46) (66 . 69) (89 . 92) 69 | (112 . 115) (137 . 140) (162 . 165)) 70 | :on "number.nim" :match 'nim-font-lock-number-face)) 71 | 72 | (it "should highlight pragmas" 73 | (assert-highlights-between 74 | '((31 . 40) (79 . 85)) 75 | :on "pragma.nim" :match 'nim-font-lock-pragma-face)) 76 | 77 | (it "should highlight docstring" 78 | (assert-highlights-between 79 | '((56 . 99) (128 . 175)) 80 | :on "docstring.nim" :match font-lock-doc-face)) 81 | 82 | (it "should highlight multi line comment and doc comment" 83 | (prepare-file "multiline_comment.nim") 84 | (assert-highlights-between 85 | '((1 . 5) (77 . 78) (80 . 185) (564 . 566) (567 . 576) (615 . 619) 86 | (630 . 634) (646 . 650) (662 . 666) (683 . 700)) 87 | :match font-lock-comment-face) 88 | (assert-highlights-between 89 | '((48 . 75) (192 . 235) (545 . 553) (611 . 614) (626 . 629) 90 | (641 . 645) (657 . 661) (673 . 682)) 91 | :match font-lock-string-face) 92 | (assert-highlights-between 93 | '((237 . 453) (482 . 536)) 94 | :match font-lock-doc-face) 95 | ;; might be added eventually 96 | ;; (assert-highlights-between 97 | ;; '() 98 | ;; :match font-lock-comment-delimiter-face) 99 | ) 100 | 101 | (it "should highlight varargs inside proc’s args correctly" 102 | (assert-highlight-between 55 61 :on "varargs.nim" 103 | :match font-lock-type-face)) 104 | 105 | (it "should highlight words inside backticks correctly" 106 | (assert-highlights-between 107 | '((100 . 115) (124 . 139) (148 . 163) (172 . 187) 108 | (196 . 211) (220 . 235) (248 . 263) (272 . 287) 109 | (322 . 337) (342 . 357)) 110 | :on "backtick.nim" 111 | :match font-lock-constant-face)) 112 | 113 | ;; $# and $[1-9] 114 | (it "should highlight $# and $[1-9] inside string correctly" 115 | (assert-highlights-between 116 | '((84 . 85) (89 . 90) (94 . 95)) 117 | :on "format.nim" :match font-lock-preprocessor-face)) 118 | 119 | ;; after ‘is’ operator and ‘distinct’ 120 | (it "should highlight after ‘is’ operator correctly" 121 | (assert-highlights-between 122 | '((132 . 142) (202 . 212) (282 . 293)) 123 | :on "test_is_and_distinct.nim" :match font-lock-type-face)) 124 | 125 | (it "should highlight single line comment correctly" 126 | ;; Note this test was added because I got wrong highlight 127 | ;; only nimscript file, so don’t change the extension name. 128 | (prepare-file "single_comment.nims") 129 | (assert-highlight-between 1 70 :match font-lock-comment-face) 130 | (assert-highlight-between 73 76 :match font-lock-keyword-face)) 131 | 132 | ;; Character 133 | (it "should highlight characters correctly" 134 | (let* ((chars (collect-char-points :on "char.nim")) 135 | (char-pos (car chars)) 136 | (after-char (cdr chars))) 137 | (assert-highlights-between char-pos :match font-lock-string-face) 138 | (assert-highlights-between after-char :notmatch font-lock-string-face))) 139 | 140 | ) ; end of describe function 141 | 142 | (describe "Syntax nimscript-mode" 143 | (before-each 144 | (set-buffer (get-buffer-create "*Test*")) 145 | (erase-buffer) 146 | ;; may need to turn off this to adapt triple double quotes. 147 | (prettify-symbols-mode 0) 148 | (nimscript-mode)) 149 | 150 | (after-each 151 | (kill-buffer (get-buffer-create "*Test*"))) 152 | 153 | (it "should highlight NimScript keywords correctly" 154 | (prepare-file "test_nimscript.nims") 155 | (assert-highlights-between 156 | '((78 . 81) (83 . 87) (145 . 149) (207 . 211) (259 . 262)) 157 | :match font-lock-builtin-face) 158 | (assert-highlight-between 38 41 :match font-lock-variable-name-face) 159 | (assert-highlight-between 45 54 :match font-lock-type-face)) 160 | 161 | ) ; end of describe for nimscript 162 | 163 | ;; Local Variables: 164 | ;; no-byte-compile: t 165 | ;; End: 166 | 167 | ;;; test-syntax.el ends here 168 | --------------------------------------------------------------------------------