├── .DS_Store
├── .gitignore
├── .vscode
└── launch.json
├── README.md
├── color_theme_compatibility
├── dark_defaults.json
├── dark_plus.json
├── dark_vs.json
└── display.mjs
├── doc
├── code_1.png
├── code_1.xi
├── code_2.png
├── code_2.xi
├── code_3.png
├── code_3.xi
├── heading.xi
├── heading_1.png
├── highlight.png
├── highlight.xi
├── link3.xi
├── link_1.png
├── link_1.xi
├── link_2.png
├── link_3.png
├── list.png
├── list.xi
├── md_to_xi_dst.png
├── md_to_xi_dst.xi
├── md_to_xi_src.md
├── md_to_xi_src.png
├── meta_heading.png
├── named_param_1.png
├── named_param_1.xi
├── named_param_2.png
├── named_param_2.xi
├── paragraph_1.png
├── paragraph_1.xi
└── xi_illustration.png
├── esbuild.mjs
├── examples
├── lua_syntax.xi
└── neurotransmitter.xi
├── icon.png
├── icon.svg
├── language-configuration.json
├── out
└── .gitignore
├── package-lock.json
├── package.json
├── src
├── extension.mjs
├── get_editor_change_handler.mjs
├── get_fold_provider.mjs
├── get_go_back_cmd.mjs
├── get_jump_to_anchor.mjs
├── get_link_provider.mjs
├── get_lookup_cmd.mjs
├── get_open_cmd.mjs
├── icon.afdesign
├── tools.mjs
├── xi-markdown-injection.tmLanguage.json
├── xi.tmLanguage.json
└── xi_parser.mjs
└── test
├── editor_change_handler.mjs
├── fold_provider.mjs
├── go_back_cmd.mjs
├── jump_test.xi
├── jump_to_anchor.mjs
├── link_provider.mjs
├── open_cmd.mjs
├── test.md
├── test.xi
└── xi_parser.mjs
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [{
3 | "name": "Debug extension",
4 | "type": "extensionHost",
5 | "request": "launch",
6 | "args": [
7 | "--extensionDevelopmentPath=${workspaceFolder}"
8 | ]
9 | }, {
10 | "name": "Debug test(s)",
11 | "type": "node",
12 | "request": "launch",
13 | "runtimeExecutable": "npm",
14 | "runtimeArgs": ["exec", "mocha"]
15 | }]
16 | }
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Xi personal wiki language
2 |
3 | "Xi" is a markdown-like language designed for a personal knowledge base.
4 | Color and indentation are used to highlight headings, paragraphs, links,
5 | source code examples and definitions. You can read and write Xi from
6 | within a VSCode without a need to "render" it into a "visual" web page
7 | or PDF. This makes Xi a very **fast** tool to keep records: you grep
8 | someting, skim through Xi pages, add or change some text from within a same
9 | editor, without a need to switch between 'edit' and 'previes' modes. An
10 | example of Xi page from my personal knowledge base with
11 | [Memory](https://marketplace.visualstudio.com/items?itemName=grigoryvp.memory-theme)
12 | color theme:
13 |
14 |
15 |
16 |
17 | ## VSCode extension installation guide
18 |
19 | This extension is published to the "Extension Marketplace". To install it,
20 | click on the "Extensions" icon in the Activity Bar on the side of VSCode
21 | and enter "Xi markup language" in the search field. Click on the "install"
22 | button near the extension listed in the search results.
23 |
24 | ## General
25 |
26 | The example below shows a simple markdown syntax with nested headings and
27 | paragraphs. With Xi, heading is marked with indentation and tail space-dot
28 | instead of indentation. So, heading 2 in markdown that is '## Heading 2'
29 | becomes ' Heading 2 .' in Xi (tail space-dot defines that it's a heading
30 | and leading two spaces defines second level. Third level will be 4 spaces
31 | and up to 10 spaces for a maximum nesting level 5). Paragraphs are marked
32 | with dot-space, effectively saving one empty line used in markdown:
33 |
34 |
35 |
36 |
37 | The extension provides a single command **extension.xi.lookup** that is binded
38 | to the **ctrl+k x** by default. This command brings file search for the
39 | directory with xi files, which is **~/.xi** by default and can be changed
40 | via the **xi.lookupPath** configuration option. It assumes workflow where if
41 | you are in any VSCode project and want to consult your personal knowledge
42 | base or add something to it, you hit "ctrl+k x", enter part of file name
43 | and open corresponding file for read and update. For example, writing some
44 | Python source code you want to remind yourself about API nuances. You
45 | know, that you store programming languages API records in files named
46 | "language_api.xi", so you hit "ctrl+k x", enter "python_a" that narrows
47 | search to "python_api.xi", hit enter, open file, check your notes and
48 | return to your code.
49 |
50 | Wikiwords and links are marked as VSCode "links" and can be opened via
51 | standard VSCode means, for example the **editor.action.openLink** keybinding.
52 |
53 | ## Paragraph
54 |
55 | Simplest building block of a knowledge base, first paragraph line is prefixed
56 | with dot-and-space, while each next line is prefixed with two spaces.
57 | Paragraph itself can be indented with increments of two spaces in order to
58 | nest under different heading or other paragraphs. Compared to markdown
59 | notion of separating paragraphs with empty line, such layout saves a lot
60 | of vertical space, which is vital for documenting software-related things
61 | like programming lanugae syntax, APIs, frameworks etc. We tend to have
62 | short paragraphs that do not tend to follow each other, but tend to form
63 | a really complex nested strucure with lots of headings, meta information,
64 | code samples etc. Such paragrapah layout, used alongside colored headings and
65 | proper indentation, proved it's worth during my 10 years of using Xi as a
66 | personal knowledge base.
67 |
68 |
69 |
70 | ## Heading
71 |
72 | Alongside paragraph, heading is a second most used building block for a
73 | knowledge base. Heading type is denoted by space-dot suffix, while heading
74 | nesting level is denoted by increments of two spaces. In most cases headings
75 | are combined with paragraphs in order to create hierarchical structures
76 | that are easy to read **and modify**.
77 |
78 |
79 |
80 | ## Links
81 |
82 | Here Xi starts to differenciate from Markdown. Simple "wikiword" link looks
83 | like one in Markdown, `[foo]`, but clicking on such link will try to open
84 | corresponding file, so everything works locally within VSCode. File name
85 | is created by lowercasing text between square braces, replacing spaces with
86 | underscores and appending `.xi` suffix. So clicking on the `[foo]` wikiword
87 | will instruct VSCode to open `foo.xi` in same folder, or ask to create one
88 | if it does not exist.
89 |
90 |
91 | 
92 |
93 | Wikiword links with anchors extend this concept by allowing to add heading
94 | name after `#`. Clicking on the like like `[js api#search]` will try to
95 | open `js_api.xi` file and scroll to the first `search` hading (that starts
96 | with spaces and ends with space-dot). Nested anchors are supported by
97 | chaining multiple '#' like `[js api#* String#- search]`.
98 |
99 | Header links transforms a header into link by adding `[] .` instead of
100 | space-and-dot. Mostly useful while describing APIs or frameworks with
101 | complex tree structures where some headers points to articles of their own.
102 | Header can also start and/or end with wikiword links.
103 |
104 |
105 | 
106 |
107 | Anchor links starts with `#` and jumps within same document (mostly used for
108 | "Reference" section at bottom). Anchor target is specified by placing
109 | `#` as a last symbol of a wikiword link, so clicking on the link `[#1]`
110 | will jump into a line with `[1#]` anchor.
111 |
112 |
113 | 
114 |
115 | ## Text highlight
116 |
117 | Xi supports a number of ways to highligh important parts of text. Pipes are
118 | used to highlight "special text" which are terms, API method names etc.
119 | Backticks are used to highlight something important with bright color. Please
120 | note that if [memory color theme](https://marketplace.visualstudio.com/items?itemName=grigoryvp.memory-theme)
121 | is used, highlight syntax is dimmed:
122 |
123 |
124 | 
125 |
126 | ## Code samples
127 |
128 | Xi was created with the main goal to keep all my notes about different
129 | programming languages, framework and APIs I use. It is tailored to
130 | represent code samples in multiple ways. Most common is a "block code
131 | sample" which is a paragraph (dot-space prefix) that starts with a pipe
132 | character, followed by optional meta information inside curly braces. Meta
133 | syntax is a list of key-value pairs, a key separated from a value with the
134 | colon character and pairs separated from each other with the semicolon
135 | character. I use the 'lng' key to denote a programming language type, with
136 | a file extension as a value. So the block code sample for a Python
137 | programming language will start with `. |{lng:py}`, which will be dimmed
138 | out by a syntax highlighter. Each line of the sample is prefixed with a
139 | pipe, indented to match the position of the pipe in the `|{lng:py}`.
140 |
141 |
142 | 
143 |
144 | Code sample can be a paragraph of it's own or appear as a part of paragraph
145 | with text:
146 |
147 |
148 | 
149 |
150 | Code sample can also be inlined into a paragraph text by starting code
151 | right after `{}` meta inormation and terminating code with a pipe. for
152 | multiline code paragraph the optional meta information line should not
153 | contain any symbols after the meta:
154 |
155 |
156 | 
157 |
158 | ## Named parameters
159 |
160 | This Xi feature is more specialized for programming languages. While
161 | taking notes on APIs it's often required to reference method parameters
162 | inside of a method description. While this can be done via the text highlight
163 | feature, doing so will not distinguish parameters among other highlighted
164 | words. Specialized syntax allows to include named parameter between curled
165 | braces. In addition, char-space prefix can be added after opening curled
166 | brace to mark input or output parameters. It's also possible to include
167 | parameter type wihin round braces after a mark character: all with syntax
168 | highlighting for a visual navigation:
169 |
170 |
171 | 
172 |
173 | Usage example that describes an "insert" method that takes two named and
174 | positional parameters, "indice" and "item".
175 |
176 |
177 | 
178 |
179 | ## Lists
180 |
181 | Like in Markdown, lists in Xi are denoted with `#`, `*` or `-` characters.
182 | The syntax highlighting behaviour is the same as with paragraphs (that start
183 | with the `.` character).
184 |
185 |
186 | 
187 |
188 | ## Meta heading
189 |
190 | Sometimes a document contains some headers that denote meta information
191 | about the document. Two common examples of such headers are document name
192 | at top and the "reference" section at bottom. For them a special coloring
193 | is provided if they are terminated with the "@" character instead of the dot:
194 |
195 |
196 | 
197 |
198 | ## Examples
199 |
200 | * [Lua syntax](https://github.com/grigoryvp/vscode-language-xi/tree/master/examples/lua_syntax.xi)
201 | * [Neurophysiology article](https://github.com/grigoryvp/vscode-language-xi/tree/master/examples/neurotransmitter.xi)
202 |
203 | ## Todo
204 |
205 | * Re-take all screenshots.
206 | * Outline API support.
207 |
208 | ## License
209 |
210 | The following licensing applies to Xi personal wiki language:
211 | Attribution-NonCommercial-NoDerivatives 4.0 International
212 | (CC-BY-NC-ND-4.0). For more information go to
213 | [https://creativecommons.org/licenses/by-nc-nd/4.0/](https://creativecommons.org/licenses/by-nc-nd/4.0/)
214 |
--------------------------------------------------------------------------------
/color_theme_compatibility/dark_defaults.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "vscode://schemas/color-theme",
3 | "name": "Dark Default Colors",
4 | "colors": {
5 | "editor.background": "#1E1E1E",
6 | "editor.foreground": "#D4D4D4",
7 | "editor.inactiveSelectionBackground": "#3A3D41",
8 | "editorIndentGuide.background": "#404040",
9 | "editorIndentGuide.activeBackground": "#707070",
10 | "editor.selectionHighlightBackground": "#ADD6FF26",
11 | "list.dropBackground": "#383B3D",
12 | "activityBarBadge.background": "#007ACC",
13 | "sideBarTitle.foreground": "#BBBBBB",
14 | "input.placeholderForeground": "#A6A6A6",
15 | "settings.textInputBackground": "#292929",
16 | "settings.numberInputBackground": "#292929"
17 | }
18 | }
--------------------------------------------------------------------------------
/color_theme_compatibility/dark_plus.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "vscode://schemas/color-theme",
3 | "name": "Dark+ (default dark)",
4 | "include": "./dark_vs.json",
5 | "tokenColors": [
6 | {
7 | "name": "Function declarations",
8 | "scope": [
9 | "entity.name.function",
10 | "support.function",
11 | "support.constant.handlebars"
12 | ],
13 | "settings": {
14 | "foreground": "#DCDCAA"
15 | }
16 | },
17 | {
18 | "name": "Types declaration and references",
19 | "scope": [
20 | "meta.return-type",
21 | "support.class",
22 | "support.type",
23 | "entity.name.type",
24 | "entity.name.class",
25 | "storage.type.numeric.go",
26 | "storage.type.byte.go",
27 | "storage.type.boolean.go",
28 | "storage.type.string.go",
29 | "storage.type.uintptr.go",
30 | "storage.type.error.go",
31 | "storage.type.rune.go",
32 | "storage.type.cs",
33 | "storage.type.generic.cs",
34 | "storage.type.modifier.cs",
35 | "storage.type.variable.cs",
36 | "storage.type.annotation.java",
37 | "storage.type.generic.java",
38 | "storage.type.java",
39 | "storage.type.object.array.java",
40 | "storage.type.primitive.array.java",
41 | "storage.type.primitive.java",
42 | "storage.type.token.java",
43 | "storage.type.groovy",
44 | "storage.type.annotation.groovy",
45 | "storage.type.parameters.groovy",
46 | "storage.type.generic.groovy",
47 | "storage.type.object.array.groovy",
48 | "storage.type.primitive.array.groovy",
49 | "storage.type.primitive.groovy"
50 | ],
51 | "settings": {
52 | "foreground": "#4EC9B0"
53 | }
54 | },
55 | {
56 | "name": "Types declaration and references, TS grammar specific",
57 | "scope": [
58 | "meta.type.cast.expr",
59 | "meta.type.new.expr",
60 | "support.constant.math",
61 | "support.constant.dom",
62 | "support.constant.json",
63 | "entity.other.inherited-class"
64 | ],
65 | "settings": {
66 | "foreground": "#4EC9B0"
67 | }
68 | },
69 | {
70 | "name": "Control flow keywords",
71 | "scope": "keyword.control",
72 | "settings": {
73 | "foreground": "#C586C0"
74 | }
75 | },
76 | {
77 | "name": "Variable and parameter name",
78 | "scope": [
79 | "variable",
80 | "meta.definition.variable.name",
81 | "support.variable",
82 | "entity.name.variable"
83 | ],
84 | "settings": {
85 | "foreground": "#9CDCFE"
86 | }
87 | },
88 | {
89 | "name": "Object keys, TS grammar specific",
90 | "scope": [
91 | "meta.object-literal.key"
92 | ],
93 | "settings": {
94 | "foreground": "#9CDCFE"
95 | }
96 | },
97 | {
98 | "name": "CSS property value",
99 | "scope": [
100 | "support.constant.property-value",
101 | "support.constant.font-name",
102 | "support.constant.media-type",
103 | "support.constant.media",
104 | "constant.other.color.rgb-value",
105 | "constant.other.rgb-value",
106 | "support.constant.color"
107 | ],
108 | "settings": {
109 | "foreground": "#CE9178"
110 | }
111 | },
112 | {
113 | "name": "Regular expression groups",
114 | "scope": [
115 | "punctuation.definition.group.regexp",
116 | "punctuation.definition.group.assertion.regexp",
117 | "punctuation.definition.character-class.regexp",
118 | "punctuation.character.set.begin.regexp",
119 | "punctuation.character.set.end.regexp",
120 | "keyword.operator.negation.regexp",
121 | "support.other.parenthesis.regexp"
122 | ],
123 | "settings": {
124 | "foreground": "#CE9178"
125 | }
126 | },
127 | {
128 | "scope": [
129 | "constant.character.character-class.regexp",
130 | "constant.other.character-class.set.regexp",
131 | "constant.other.character-class.regexp",
132 | "constant.character.set.regexp"
133 | ],
134 | "settings": {
135 | "foreground": "#d16969"
136 | }
137 | },
138 | {
139 | "scope": [
140 | "keyword.operator.or.regexp",
141 | "keyword.control.anchor.regexp"
142 | ],
143 | "settings": {
144 | "foreground": "#DCDCAA"
145 | }
146 | },
147 | {
148 | "scope": "keyword.operator.quantifier.regexp",
149 | "settings": {
150 | "foreground": "#d7ba7d"
151 | }
152 | },
153 | {
154 | "scope": "constant.character",
155 | "settings": {
156 | "foreground": "#569cd6"
157 | }
158 | },
159 | {
160 | "scope": "constant.character.escape",
161 | "settings": {
162 | "foreground": "#d7ba7d"
163 | }
164 | }
165 | ]
166 | }
--------------------------------------------------------------------------------
/color_theme_compatibility/dark_vs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "vscode://schemas/color-theme",
3 | "name": "Dark (Visual Studio)",
4 | "include": "./dark_defaults.json",
5 | "tokenColors": [
6 | {
7 | "scope": [
8 | "meta.embedded",
9 | "source.groovy.embedded"
10 | ],
11 | "settings": {
12 | "foreground": "#D4D4D4"
13 | }
14 | },
15 | {
16 | "scope": "emphasis",
17 | "settings": {
18 | "fontStyle": "italic"
19 | }
20 | },
21 | {
22 | "scope": "strong",
23 | "settings": {
24 | "fontStyle": "bold"
25 | }
26 | },
27 | {
28 | "scope": "header",
29 | "settings": {
30 | "foreground": "#000080"
31 | }
32 | },
33 | {
34 | "scope": "comment",
35 | "settings": {
36 | "foreground": "#6A9955"
37 | }
38 | },
39 | {
40 | "scope": "constant.language",
41 | "settings": {
42 | "foreground": "#569cd6"
43 | }
44 | },
45 | {
46 | "scope": [
47 | "constant.numeric"
48 | ],
49 | "settings": {
50 | "foreground": "#b5cea8"
51 | }
52 | },
53 | {
54 | "scope": "constant.regexp",
55 | "settings": {
56 | "foreground": "#646695"
57 | }
58 | },
59 | {
60 | "scope": "entity.name.tag",
61 | "settings": {
62 | "foreground": "#569cd6"
63 | }
64 | },
65 | {
66 | "scope": "entity.name.tag.css",
67 | "settings": {
68 | "foreground": "#d7ba7d"
69 | }
70 | },
71 | {
72 | "scope": "entity.other.attribute-name",
73 | "settings": {
74 | "foreground": "#9cdcfe"
75 | }
76 | },
77 | {
78 | "scope": [
79 | "entity.other.attribute-name.class.css",
80 | "entity.other.attribute-name.class.mixin.css",
81 | "entity.other.attribute-name.id.css",
82 | "entity.other.attribute-name.parent-selector.css",
83 | "entity.other.attribute-name.pseudo-class.css",
84 | "entity.other.attribute-name.pseudo-element.css",
85 | "source.css.less entity.other.attribute-name.id",
86 | "entity.other.attribute-name.attribute.scss",
87 | "entity.other.attribute-name.scss"
88 | ],
89 | "settings": {
90 | "foreground": "#d7ba7d"
91 | }
92 | },
93 | {
94 | "scope": "invalid",
95 | "settings": {
96 | "foreground": "#f44747"
97 | }
98 | },
99 | {
100 | "scope": "markup.underline",
101 | "settings": {
102 | "fontStyle": "underline"
103 | }
104 | },
105 | {
106 | "scope": "markup.bold",
107 | "settings": {
108 | "fontStyle": "bold",
109 | "foreground": "#569cd6"
110 | }
111 | },
112 | {
113 | "scope": "markup.heading",
114 | "settings": {
115 | "fontStyle": "bold",
116 | "foreground": "#569cd6"
117 | }
118 | },
119 | {
120 | "scope": "markup.italic",
121 | "settings": {
122 | "fontStyle": "italic"
123 | }
124 | },
125 | {
126 | "scope": "markup.inserted",
127 | "settings": {
128 | "foreground": "#b5cea8"
129 | }
130 | },
131 | {
132 | "scope": "markup.deleted",
133 | "settings": {
134 | "foreground": "#ce9178"
135 | }
136 | },
137 | {
138 | "scope": "markup.changed",
139 | "settings": {
140 | "foreground": "#569cd6"
141 | }
142 | },
143 | {
144 | "scope": "beginning.punctuation.definition.quote.markdown",
145 | "settings": {
146 | "foreground": "#6A9955"
147 | }
148 | },
149 | {
150 | "scope": "beginning.punctuation.definition.list.markdown",
151 | "settings": {
152 | "foreground": "#6796e6"
153 | }
154 | },
155 | {
156 | "scope": "markup.inline.raw",
157 | "settings": {
158 | "foreground": "#ce9178"
159 | }
160 | },
161 | {
162 | "scope": "meta.selector",
163 | "settings": {
164 | "foreground": "#d7ba7d"
165 | }
166 | },
167 | {
168 | "name": "brackets of XML/HTML tags",
169 | "scope": "punctuation.definition.tag",
170 | "settings": {
171 | "foreground": "#808080"
172 | }
173 | },
174 | {
175 | "scope": "meta.preprocessor",
176 | "settings": {
177 | "foreground": "#569cd6"
178 | }
179 | },
180 | {
181 | "scope": "meta.preprocessor.string",
182 | "settings": {
183 | "foreground": "#ce9178"
184 | }
185 | },
186 | {
187 | "scope": "meta.preprocessor.numeric",
188 | "settings": {
189 | "foreground": "#b5cea8"
190 | }
191 | },
192 | {
193 | "scope": "meta.structure.dictionary.key.python",
194 | "settings": {
195 | "foreground": "#9cdcfe"
196 | }
197 | },
198 | {
199 | "scope": "meta.diff.header",
200 | "settings": {
201 | "foreground": "#569cd6"
202 | }
203 | },
204 | {
205 | "scope": "storage",
206 | "settings": {
207 | "foreground": "#569cd6"
208 | }
209 | },
210 | {
211 | "scope": "storage.type",
212 | "settings": {
213 | "foreground": "#569cd6"
214 | }
215 | },
216 | {
217 | "scope": "storage.modifier",
218 | "settings": {
219 | "foreground": "#569cd6"
220 | }
221 | },
222 | {
223 | "scope": "string",
224 | "settings": {
225 | "foreground": "#ce9178"
226 | }
227 | },
228 | {
229 | "scope": "string.tag",
230 | "settings": {
231 | "foreground": "#ce9178"
232 | }
233 | },
234 | {
235 | "scope": "string.value",
236 | "settings": {
237 | "foreground": "#ce9178"
238 | }
239 | },
240 | {
241 | "scope": "string.regexp",
242 | "settings": {
243 | "foreground": "#d16969"
244 | }
245 | },
246 | {
247 | "name": "String interpolation",
248 | "scope": [
249 | "punctuation.definition.template-expression.begin",
250 | "punctuation.definition.template-expression.end",
251 | "punctuation.section.embedded"
252 | ],
253 | "settings": {
254 | "foreground": "#569cd6"
255 | }
256 | },
257 | {
258 | "name": "Reset JavaScript string interpolation expression",
259 | "scope": [
260 | "meta.template.expression"
261 | ],
262 | "settings": {
263 | "foreground": "#d4d4d4"
264 | }
265 | },
266 | {
267 | "scope": [
268 | "support.type.vendored.property-name",
269 | "support.type.property-name",
270 | "variable.css",
271 | "variable.scss",
272 | "variable.other.less",
273 | "source.coffee.embedded"
274 | ],
275 | "settings": {
276 | "foreground": "#9cdcfe"
277 | }
278 | },
279 | {
280 | "scope": "keyword",
281 | "settings": {
282 | "foreground": "#569cd6"
283 | }
284 | },
285 | {
286 | "scope": "keyword.control",
287 | "settings": {
288 | "foreground": "#569cd6"
289 | }
290 | },
291 | {
292 | "scope": "keyword.operator",
293 | "settings": {
294 | "foreground": "#d4d4d4"
295 | }
296 | },
297 | {
298 | "scope": [
299 | "keyword.operator.new",
300 | "keyword.operator.expression",
301 | "keyword.operator.cast",
302 | "keyword.operator.sizeof",
303 | "keyword.operator.instanceof",
304 | "keyword.operator.logical.python"
305 | ],
306 | "settings": {
307 | "foreground": "#569cd6"
308 | }
309 | },
310 | {
311 | "scope": "keyword.other.unit",
312 | "settings": {
313 | "foreground": "#b5cea8"
314 | }
315 | },
316 | {
317 | "scope": [
318 | "punctuation.section.embedded.begin.php",
319 | "punctuation.section.embedded.end.php"
320 | ],
321 | "settings": {
322 | "foreground": "#569cd6"
323 | }
324 | },
325 | {
326 | "scope": "support.function.git-rebase",
327 | "settings": {
328 | "foreground": "#9cdcfe"
329 | }
330 | },
331 | {
332 | "scope": "constant.sha.git-rebase",
333 | "settings": {
334 | "foreground": "#b5cea8"
335 | }
336 | },
337 | {
338 | "name": "coloring of the Java import and package identifiers",
339 | "scope": [
340 | "storage.modifier.import.java",
341 | "variable.language.wildcard.java",
342 | "storage.modifier.package.java"
343 | ],
344 | "settings": {
345 | "foreground": "#d4d4d4"
346 | }
347 | },
348 | {
349 | "name": "this.self",
350 | "scope": "variable.language",
351 | "settings": {
352 | "foreground": "#569cd6"
353 | }
354 | }
355 | ]
356 | }
--------------------------------------------------------------------------------
/color_theme_compatibility/display.mjs:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import theme_default from './dark_defaults.json' assert {type: 'json'};
3 | import theme_vs from './dark_vs.json' assert {type: 'json'};
4 | import theme_plus from './dark_plus.json' assert {type: 'json'};
5 |
6 | const colorBg = theme_default.colors['editor.background'].toLowerCase();
7 | const colorFg = theme_default.colors['editor.foreground'].toLowerCase();
8 | const tokenSeq = [...theme_vs.tokenColors, ...theme_plus.tokenColors];
9 |
10 | const scopeTree = {color: '', childMap: {}};
11 | const colorMap = new Set();
12 |
13 | for (const token of tokenSeq) {
14 | if (!token.scope || !token.settings.foreground) continue;
15 | if (!Array.isArray(token.scope)) token.scope = [token.scope];
16 | for (const scope of token.scope) {
17 | let root = scopeTree;
18 | const partSeq = scope.split('.');
19 | for (let i = 0; i < partSeq.length; i ++) {
20 | const part = partSeq[i];
21 | if (!root.childMap[part]) root.childMap[part] = {childMap: {}};
22 | root = root.childMap[part];
23 | if (i === partSeq.length - 1) {
24 | root.color = token.settings.foreground.toLowerCase();
25 | colorMap[root.color] = true;
26 | }
27 | }
28 | }
29 | }
30 |
31 | function drawScopeRecursive(childMap, offset = 0) {
32 | for (const [key, val] of Object.entries(childMap)) {
33 | let str = "";
34 | for (let i = 0; i < offset; i ++) str += " ";
35 | str += key;
36 | if (val.color) {
37 | console.log(chalk.bgHex(colorBg).hex(val.color)(str));
38 | }
39 | else {
40 | console.log(chalk.bgHex(colorBg).hex('#888')(str));
41 | }
42 | drawScopeRecursive(val.childMap, offset + 1);
43 | }
44 | }
45 |
46 | function allScopesForColor(childMap, color, path = []) {
47 | let res = [];
48 | for (const [key, val] of Object.entries(childMap)) {
49 | if (val.color === color) res.push([...path, key].join('.'));
50 | res.push(...allScopesForColor(val.childMap, color, [...path, key]));
51 | }
52 | return res;
53 | }
54 |
55 | console.log(`colors: ${Object.keys(colorMap).length}`);
56 | console.log(chalk.bgHex(colorBg).hex(colorFg)(colorFg));
57 | for (const color of Object.keys(colorMap)) {
58 | const scopeSeq = allScopesForColor(scopeTree.childMap, color);
59 | const count = String(scopeSeq.length).padEnd(2);
60 | const scope = scopeSeq.sort()[0];
61 | let str = `${color} ${count} ${scope}`;
62 | console.log(chalk.bgHex(colorBg).hex(color)(str));
63 | }
64 |
--------------------------------------------------------------------------------
/doc/code_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/code_1.png
--------------------------------------------------------------------------------
/doc/code_1.xi:
--------------------------------------------------------------------------------
1 | . |{lng:py;file:foo.py}
2 | | print("paragraph")
3 |
--------------------------------------------------------------------------------
/doc/code_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/code_2.png
--------------------------------------------------------------------------------
/doc/code_2.xi:
--------------------------------------------------------------------------------
1 | . Paragraph with a
2 | |{lng:py;file:foo.py}
3 | | print("code sample")
4 |
--------------------------------------------------------------------------------
/doc/code_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/code_3.png
--------------------------------------------------------------------------------
/doc/code_3.xi:
--------------------------------------------------------------------------------
1 | . Inline |{lng:py}"code"| sample.
2 |
--------------------------------------------------------------------------------
/doc/heading.xi:
--------------------------------------------------------------------------------
1 | Heading 1 .
2 | One more heading 1 .
3 | Heading 2 .
4 | Heading 3 .
5 | Heading 4 .
6 | Heading 5 .
7 | Heading 6 .
8 | No more .
9 |
--------------------------------------------------------------------------------
/doc/heading_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/heading_1.png
--------------------------------------------------------------------------------
/doc/highlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/highlight.png
--------------------------------------------------------------------------------
/doc/highlight.xi:
--------------------------------------------------------------------------------
1 | . We `highlight` part of text and |Term| within it.
2 |
--------------------------------------------------------------------------------
/doc/link3.xi:
--------------------------------------------------------------------------------
1 | . Basic loop is 100 times faster compared to [python]/[ruby]/[php] [#1].
2 |
3 | Reference .
4 | . [1#] |http://habrahabr.ru/post/162885/#comment_5598039|.
5 |
--------------------------------------------------------------------------------
/doc/link_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/link_1.png
--------------------------------------------------------------------------------
/doc/link_1.xi:
--------------------------------------------------------------------------------
1 | Python .
2 | . [about python].
3 | . [python syntax].
4 | . [python ecosystem].
5 |
--------------------------------------------------------------------------------
/doc/link_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/link_2.png
--------------------------------------------------------------------------------
/doc/link_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/link_3.png
--------------------------------------------------------------------------------
/doc/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/list.png
--------------------------------------------------------------------------------
/doc/list.xi:
--------------------------------------------------------------------------------
1 | * Unordered list first item.
2 | * Unordered list second item.
3 | # Ordered sublist first item.
4 | # Ordered sublist second item.
5 |
--------------------------------------------------------------------------------
/doc/md_to_xi_dst.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/md_to_xi_dst.png
--------------------------------------------------------------------------------
/doc/md_to_xi_dst.xi:
--------------------------------------------------------------------------------
1 | Heading 1 .
2 | . First paragraph
3 | multiline text.
4 | . Second paragraph text.
5 | Heading 2 .
6 | . Third paragraph text.
7 |
--------------------------------------------------------------------------------
/doc/md_to_xi_src.md:
--------------------------------------------------------------------------------
1 | # Heading 1
2 | First paragraph
3 | multiline text.
4 |
5 | Second paragraph text.
6 | ## Heading 2
7 | Third paragraph text.
8 |
--------------------------------------------------------------------------------
/doc/md_to_xi_src.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/md_to_xi_src.png
--------------------------------------------------------------------------------
/doc/meta_heading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/meta_heading.png
--------------------------------------------------------------------------------
/doc/named_param_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/named_param_1.png
--------------------------------------------------------------------------------
/doc/named_param_1.xi:
--------------------------------------------------------------------------------
1 | . {param-name} is not a |term|.
2 | . Input {i named_param}.
3 | . Output typed {o(str) param}.
4 |
--------------------------------------------------------------------------------
/doc/named_param_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/named_param_2.png
--------------------------------------------------------------------------------
/doc/named_param_2.xi:
--------------------------------------------------------------------------------
1 | - insert .
2 | . After specified {i indice} inserts specified {i item}.
3 |
--------------------------------------------------------------------------------
/doc/paragraph_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/paragraph_1.png
--------------------------------------------------------------------------------
/doc/paragraph_1.xi:
--------------------------------------------------------------------------------
1 | . This is a single-single line paragraph.
2 | . And this is a multi-line paragraph that
3 | uses two spaces to indent a second line.
4 | . This is a nested paragraph. Normally
5 | we use headings with nested paragraphs.
6 | . Paragraphs without headings is a mess.
7 |
--------------------------------------------------------------------------------
/doc/xi_illustration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/doc/xi_illustration.png
--------------------------------------------------------------------------------
/esbuild.mjs:
--------------------------------------------------------------------------------
1 | import * as esbuild from 'esbuild';
2 |
3 | esbuild.build({
4 | bundle: true,
5 | outfile: './out/extension.js',
6 | entryPoints: ['./src/extension.mjs'],
7 | external: ['vscode', 'process', 'util', 'path', 'fs', 'os'],
8 | format: 'cjs',
9 | platform: 'neutral',
10 | });
11 |
--------------------------------------------------------------------------------
/examples/lua_syntax.xi:
--------------------------------------------------------------------------------
1 | Lua syntax @
2 |
3 | . [lua table].
4 | . [lua error handling].
5 | . [lua metatable].
6 | . [lua metamethod].
7 | . [lua garbage collector].
8 | . [lua multiple assignment].
9 |
10 | General .
11 | . Case-sensitive.
12 | . First-class functions.
13 |
14 | Nonusual .
15 | . Items are indexed from 1.
16 | . Referencing unbound name evaluates to |nil|.
17 | . All variables are in global scope by default, unless |local| keyword.
18 | . Strings are 8-bit without terminating null, no [unicode] support.
19 | . 0, empty string and empty containers are coerced into |true|.
20 | . All [composite data type]s (list, map etc) are single [lua table] type.
21 | . Using length on non-sequence tables with holes is implementation-specific.
22 | . All numbers are 64-bit [float]s.
23 | . Has |goto| statement.
24 | . Strings concatenated via |..|, not |+|.
25 | . No |++| or |+=|.
26 | . "Not equal" is |~=|.
27 | . |for| range includes both ends.
28 | . |#| "length" prefix operator.
29 | . |else if| is |elseif|, not |elif| or |else if|.
30 | . |repeat| with |until| loop.
31 | . No |continue| for loops.
32 | . Calls with one string or table param don't need parens.
33 | . "Instance methods" [lua metatable] is set for strings, but not tables.
34 |
35 | Comment[] .
36 | . Line comments are from |--| to |EOL|.
37 | . Block comments are from |--[[| to |]]|.
38 |
39 | Entry point .
40 | . Not required.
41 |
42 | Name binding[] .
43 | Scope[] .
44 | . Expressions are evaluated and then assigned.
45 | |{lng:lua}
46 | | x = y + 2 -- first evaluate 'y + 2', than assign result to 'x'
47 | . [Augmented assignment] not implemented due to a single-pass compiler.
48 | . [Destructuring assignment] via [lua multiple assignment].
49 | . Name is any string of Latin letters, Arabic-Indic digits, and underscores,
50 | not beginning with a digit and not being a reserved word.
51 | . As a convention, programs should avoid creating names that start with
52 | an underscore followed by one or more uppercase letters, ex |_VERSION|.
53 | . Variable defined in a loop operator is in operator's body scope, ex:
54 | |{lng:lua}
55 | | for i = 1, 2 do foo() end print(i) -- prints nil, 'i' is out of scope
56 | All other variables defined in Lua default to the `global` scope unless
57 | they use `local` keyword, ex:
58 | |{lng:lua}
59 | | function foo() a = 1 end
60 | | print( a ) -- nil
61 | | foo(); print( a ) -- 1, in foo() 'a' is defined in global scope
62 | | function bar() local b = 1 end
63 | | bar(); print( b ) -- nil, 'b' is in bar()'s scope, not in our scope
64 |
65 | Data type[] .
66 | Primitive data type[] .
67 | . No primitive data types, everything is an object.
68 | Composite data type[] .
69 | . Via [lua table]s.
70 | Anonymous data type[] .
71 | . Via [lua table]s.
72 | Nullable data type[] .
73 | . The type |nil| has one single value, |nil|.
74 | Boolean data type[] .
75 | . The type |boolean| has two values, |false| and |true|.
76 | . |false| and |nil| are caerced to |false|, everything else to |true|
77 | ! Including 0, "", empty containers.
78 | . No |boolean| type before [lua version#5.0], |nil| was used instead.
79 | Literal constructor[] .
80 | . [lua table#literal constructor].
81 | . A raw string "long literal" starts with "[", followed by zero or more "="
82 | and then "[" again. Ends with the matching square braces and equals:
83 | |{lng:lua}
84 | | level0 = [[
85 | | foo
86 | | ]]
87 | | level2 = [==[ bar ]==]
88 | String .
89 | . Immutable.
90 | . Strings can contain any 8-bit value, including embedded zeros ('\0'):
91 | |{lng:lua}
92 | | a, b, c = "test", 'test', [[test]]
93 | Unicode .
94 | . Not supported, strings are bytes, third-party libs are used.
95 | Escape .
96 | . [c]-like escape sequences recognized within quoted strings.
97 | . The escape sequence |\z| skips the following span of whitespace
98 | characters, including line breaks.
99 | . The [utf-8] encoding of a Unicode character can be inserted in
100 | a literal string with the escape sequence |\u{XXX}| (with mandatory
101 | enclosing braces), where XXX is a sequence of one or more hexadecimal
102 | digits representing the character code point.
103 | Concatenation .
104 | . Via [lua syntax#operator#concatenation].
105 | Substring .
106 | . Via [lua api#$ string].
107 | Length .
108 | . Via the [lua syntax#operator#length].
109 | Line span .
110 | . Via "long literal" constructor.
111 | String interpolation[] .
112 | . Via [lua api#string#format].
113 | List[] .
114 | . Emulated via general [lua table] data type.
115 | Set[] .
116 | . Emulated via general [lua table]s:
117 | |{lng:lua}
118 | | local set = {pear = true, plum = true}
119 | Map[] .
120 | . Emulated via general [lua table] data type.
121 | Enum .
122 | . Emulated via general [lua table]s:
123 | |{lng:lua}
124 | | Colors = {RED = 1, GREEN = 2, BLUE = 3}
125 | Tuple[] .
126 | . Via [lua multiple assignment].
127 | Range[] .
128 | . Built into [lua syntax#for loop] syntax.
129 | Type conversion[] .
130 | . See [lua syntax#boolean data type].
131 | . [lua api#string#format] converts numbers to strings.
132 | . [lua api#tonumber] explicitly converts strings to numbers.
133 | Type coercion[] .
134 | . Bitwise operators always convert float operands to integers.
135 | . Exp and float division always convert integer operands to floats. All
136 | other arithmetic operations applied to mixed numbers (integers and
137 | floats) convert the integer operand to a float.
138 | . String concatenation accepts numbers as arguments, besides strings.
139 | . Conversion from float to integer fails if no exact representation exists.
140 | . The string library sets [lua metamethod]s that try to coerce
141 | [lua api#* string] to numbers in all arithmetic operations. They are not
142 | always applied; in particular, |"1"==1| is false and |"1"<1| raises
143 | an error. These coercions exist mainly for compatibility and may be
144 | removed in future versions of the language.
145 | Type inference[] .
146 | . Not needed for dynamic language.
147 | Object copy[] .
148 | . [shallow copy] array [lua table] via [lua api#$ table#unpack].
149 |
150 | Operator[] .
151 | . Supports most [c syntax#operator]s.
152 | . Power operator |^|.
153 | . |{lng:lua}
154 | | 3 / 2 -- 1.5, all numbers are float
155 | . Ternary emulated via [programming language tricks]: "and or", ex:
156 | |{lng:lua}
157 | | a = b and c or d -- c must be not nil and not false
158 | Bitwise .
159 | . Supported since [lua version#5.3], |bit32| library before that.
160 | . |&|, |||, |^|, |~| ("exclusive or", "unary not"), |<<|, |>>|.
161 | Logical .
162 | . [short circuit logic].
163 | . |and|, |or|, |not|, |>|, |>=|, |<|, |<=|.
164 | |{lng:lua}
165 | | not false == true; not nil == true; -- everything else, ex 0, is false
166 | | - a -- -(-1) == 1
167 | | not foo and not bar or baz -- ((not foo) and (not bar)) or baz
168 | Value equality .
169 | . |==| and |~=| uses following logic:
170 | * |false| if types are different.
171 | * |true| if value is same for strings and numbers. Do not convert
172 | strings to numbers or vice versa.
173 | * |true| if [lua table], |userdate| or |thread| reference the same object:
174 | |{lng:lua}
175 | | {} == {} -- false; references to two different objects
176 | * [lua metamethod#__eq], if any.
177 | Object identity test operator[] .
178 | . [lua api#rawequal].
179 | Sequence membership .
180 | . Accessing [lua table] key without a value evaluats to |nil|.
181 | Operator overload[] .
182 | . Via [lua metamethod]s.
183 | Length .
184 | . |#| prefix operator that maps to the [lua metamethod#__len]:
185 | |{lng:lua}
186 | | len1, len2 = # str1, #str2
187 | . Applied on a [lua table] returns a border in that table (index present in
188 | the table that is followed by an absent index; or zero, when index 1 is
189 | absent).
190 | Concatenation .
191 | . |..| operator that maps to the [lua metamethod#__concat].
192 |
193 | Expression[] .
194 | Compound .
195 | . Operators and |()|.
196 | Conditional .
197 | . Via [lua syntax#operator#logical].
198 | Relational .
199 | . |{lng:lua}
200 | | a == 1 -- evaluates to true or false
201 |
202 | Statement[] .
203 | . Can be separated with optional |;|.
204 | . Newlines are same token separators as spaces:
205 | |{lng:lua}
206 | | foo =
207 | | 2
208 | If-then-else .
209 | . |{lng:lua}
210 | | if a > 0 then print(a) end
211 | | if a > 0 then print(a) else print("b") end
212 | | if a > 0 then print(a) elseif a < 0 print("-") end
213 | For loop .
214 | . |{lng:lua}
215 | | for i = 1, 5 do print(i) end -- range includes both ends
216 | | for i = 5, 1, -1 do print(i) end -- optional step
217 | For iterator .
218 | . Via [lua api#pairs], [lua api#ipairs], [lua api#next].
219 | While loop .
220 | . |{lng:lua}
221 | | while foo() do bar() end
222 | Do-while loop .
223 | . |{lng:lua}
224 | | repeat foo() until bar()
225 | Break loop .
226 | . |{lng:lua}
227 | | break
228 | . Must be last statement in a block (becouse breaks block), ex:
229 | |{lng:lua}
230 | | while true do if a > 5 then a = 0; break end end -- ok
231 | | while true do if a > 5 then break; a = 0 end -- syntax error
232 |
233 | Callable[] .
234 | . Calls with one string or table param don't need parens:
235 | |{lng:lua}
236 | | print "foo"
237 | | print {bar = 1}
238 | | print 1 -- syntax error
239 | . |function|, |end| pair forms an expression:
240 | |{lng:lua}
241 | | hs.hotkey.bind({}, "capslock", function() hs.alert.show("debug") end)
242 | Procedure .
243 | . |{lng:lua}
244 | | bar = {}
245 | | local function foo(x) return x * 2 end
246 | | function bar.baz() return ":)" end
247 | This is a syntax sugar to function [definition] assignment [statement]:
248 | |{lng:lua}
249 | | local foo; foo = function (x) return x * 2 end
250 | | bar.baz = function() return ":)" end
251 | Parameter[] .
252 | . When a Lua function is called, it adjusts its list of arguments to
253 | the length of its list of parameters, unless the function is a vararg
254 | function, which is indicated by |...| at the end of its parameter list.
255 | . |...| within vararg functin expands into "rest arguments":
256 | |{lng:lua}
257 | | function foo(a, b, ...)
258 | | c, d = ... -- 3, 4
259 | | end
260 | | foo(1, 2, 3, 4)
261 | . |...| can be used with [lua table] constructor:
262 | |{lng:lua}
263 | | function foo(...) for k, v in pairs({...}) do print(k, v) end end
264 | Return[] .
265 | . Multiple return value emulated via [lua multiple assignment].
266 | Anonymous function .
267 | . As in [js] and [scheme] all functions are anonymous. A "named
268 | function" is a variable holding a reference to a function object.
269 | . Functions encloses parent scope(s).
270 |
271 | Class[] .
272 | . |:| during a call is a syntactic sugar to pass the "call receiver" as
273 | a first argument. Following lines generate the same bytecode:
274 | |{lng:lua}
275 | | foo:bar()
276 | | foo.bar(foo)
277 | . |:| during callable declaration is a syntactic sugar that creates
278 | callable with a hidden first parameter named |self| (and sets this
279 | callable as an attribute for a left-hand lua table). Following lines
280 | generate the same bytecode:
281 | |{lng:lua}
282 | | function obj:foo() end
283 | | function obj.foo(self) end
284 | | obj.foo = function(self) end
285 | . Class emulation via |self| and [lua metamethod#__index]:
286 | |{lng:lua}
287 | | -- 'Dog' is class object
288 | | function Dog:new()
289 | | local inst = {sound = 'woof'}
290 | | -- Use the class object for attributes not found on the instance object
291 | | return setmetatable(inst, {__index = self})
292 | | end
293 | | -- Method is bound to the class object, but available via metatable
294 | | function Dog:makeSound()
295 | | -- 'self' here will be an instance object (call receiver)
296 | | print('I say ' .. self.sound)
297 | | end
298 | | mrDog = Dog:new()
299 | | mrDog:makeSound()
300 | . It'a also ideomatic to set class object as a metatable for class instances,
301 | so special [lua metamethod]s can be defined on class object:
302 | |{lng:lua}
303 | | function Dog:new()
304 | | self.__index = self
305 | | return setmetatable({}, self)
306 | | end
307 |
308 | Inheritance[] .
309 |
310 | Interface[] .
311 | Abstract class[] .
312 |
313 | Generic[] .
314 |
315 | Decorator[] .
316 |
317 | Property[] .
318 |
319 | Category[] .
320 | Mixin[] .
321 |
322 | Generator[] .
323 |
324 | Preprocessor[] .
325 |
326 | Dependency management[] .
327 | . |a.lua|:
328 | |{lng:lua}
329 | | function foo() print("foo") end
330 | | local val = 1
331 | |b.lua|:
332 | |{lng:lua}
333 | | require "a"
334 | | a.foo()
335 | | print(a.val) -- error, package local
336 | Build-in table |package| allows to enumerate and manage modules:
337 | |{lng:lua}
338 | | for k, v in pairs(package) do print(k, v) end -- list packages
339 | | package.loaded.a = nil; require "a" -- reload package
340 | . |require| searches directories specified in the |package.path|, which
341 | can be set via the |LUA_PATH| environment variable:
342 | |{lng:lua}
343 | | package.path = package.path .. ";/Users/user/dotfiles/hammerspoon/?.lua"
344 | | require "lib"
345 | | someNameFromLibNamespace()
346 |
347 | Error handling[] .
348 | . [lua error handling].
349 |
350 | Reflection[] .
351 | . [lua debug].
352 |
353 | Test[] .
354 | Assert .
355 | . |{lng:lua}
356 | | assert(exp); assert(exp. "error")
357 | ! |assert(0)| don't work, use |assert(nil)| or |assert(false)|.
358 |
--------------------------------------------------------------------------------
/examples/neurotransmitter.xi:
--------------------------------------------------------------------------------
1 | Neurotransmitter @
2 |
3 | . [synapse].
4 |
5 | . Kind of [neuromediator].
6 | . English literature reference both [neurotransmitter]s and [neuromodulator]s
7 | as "neurotransmitters".
8 | . Neurotransmitter can be |Excitatory| for some receptors and |Excitatory| to
9 | other. Грубая оценка: глутамат возбуждающий, ГАМК тормозный, ацетилхолин
10 | возбуждающий, глицин тормозный. Всё. По остальным уже больше "это зависит".
11 | . Monoamine neurotransmitters control epigenetic regulation of genes [#1].
12 | . By using at least fifty different chemical signals, the brain can carry on
13 | many different conversations at once: each neurotransmitter stimulates
14 | a different set of cells or alters their sensitivity to different chemical
15 | messengers.
16 |
17 | Excitatory .
18 | Возбуждающие .
19 | . [glutamate].
20 | . [acetylcholine].
21 | . [dopamine].
22 | . [adenosine].
23 | . [noradrenaline].
24 |
25 | Inhibitory .
26 | Тормозящие .
27 | Ингибирующие .
28 | . Makes neuron less likely to fire an action potential or release
29 | neurotransmitters.
30 | . [gaba] (ГАМК).
31 | . [glycine].
32 | . [adenosine].
33 | . [serotonin].
34 |
35 | Reference @
36 | . [1#] [https://bit.ly/31RfKw9].
37 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/icon.png
--------------------------------------------------------------------------------
/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
27 |
--------------------------------------------------------------------------------
/language-configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "comments": {
3 | "lineComment": "|",
4 | "blockComment": []
5 | },
6 | "brackets": [],
7 | "autoClosingPairs": [
8 | ["{", "}"],
9 | ["[", "]"],
10 | ["(", ")"],
11 | ["\"", "\""],
12 | ["'", "'"],
13 | ["`", "`"]
14 | ],
15 | "surroundingPairs": [
16 | ["{", "}"],
17 | ["[", "]"],
18 | ["(", ")"],
19 | ["\"", "\""],
20 | ["'", "'"],
21 | ["`", "`"],
22 | ["|", "|"]
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/out/.gitignore:
--------------------------------------------------------------------------------
1 | *.*
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "language-xi",
3 | "displayName": "Xi markup language",
4 | "description": "Syntax highlighting and wikiword jumps support for Xi markup language",
5 | "repository": "https://github.com/grigoryvp/vscode-language-xi",
6 | "icon": "icon.png",
7 | "version": "1.10.67",
8 | "publisher": "grigoryvp",
9 | "license": "SEE LICENSE IN README.md",
10 | "engines": {
11 | "vscode": ">=1.75.0"
12 | },
13 | "categories": [
14 | "Other"
15 | ],
16 | "scripts": {
17 | "vscode:prepublish": "node esbuild.mjs",
18 | "color-theme": "node --no-warnings ./color_theme_compatibility/display.mjs",
19 | "test": "npm exec mocha",
20 | "build": "vsce package --out ./out/extension.vsix",
21 | "build-pre-release": "vsce package --pre-release --out ./out/extension.vsix",
22 | "publish": "open-cli https://marketplace.visualstudio.com/manage"
23 | },
24 | "main": "./out/extension.js",
25 | "browser": "./out/extension.js",
26 | "files": [
27 | "./out/extension.js",
28 | "./src/*.json"
29 | ],
30 | "contributes": {
31 | "languages": [{
32 | "id": "xi",
33 | "extensions": [
34 | ".xi"
35 | ],
36 | "configuration": "./language-configuration.json"
37 | }, {
38 | "id": "xi-markdown-injection"
39 | }],
40 | "grammars": [{
41 | "language": "xi",
42 | "scopeName": "text.xi",
43 | "path": "./src/xi.tmLanguage.json"
44 | }, {
45 | "language": "xi-markdown-injection",
46 | "scopeName": "markdown.xi.codeblock",
47 | "injectTo": [
48 | "text.html.markdown"
49 | ],
50 | "embeddedLanguages": {
51 | "meta.embedded.block.xi": "xi"
52 | },
53 | "path": "./src/xi-markdown-injection.tmLanguage.json"
54 | }],
55 | "commands": [{
56 | "command": "extension.xi.lookup",
57 | "title": "Xi: Look up a .xi file",
58 | "when": "!isWeb"
59 | }],
60 | "configuration": {
61 | "type": "object",
62 | "title": "Xi configuration",
63 | "properties": {
64 | "xi.lookupPath": {
65 | "type": "string",
66 | "default": "~/.xi",
67 | "description": "Path to a dir with .xi files"
68 | },
69 | "xi.debug": {
70 | "type": "boolean",
71 | "default": false,
72 | "description": "true to enable debug output"
73 | }
74 | }
75 | },
76 | "keybindings": [{
77 | "command": "extension.xi.lookup",
78 | "key": "ctrl+k x",
79 | "mac": "cmd+k x",
80 | "when": "!isWeb"
81 | }]
82 | },
83 | "devDependencies": {
84 | "@types/chai": "^4.3.11",
85 | "@vscode/vsce": "^2.22.0",
86 | "chai": "^4.3.10",
87 | "chalk": "^5.3.0",
88 | "esbuild": "^0.19.11",
89 | "mocha": "^10.8.2",
90 | "open-cli": "^7.2.0"
91 | },
92 | "dependencies": {
93 | "vscode-uri": "^3.0.8"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/extension.mjs:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { Utils } from 'vscode-uri'
3 | import getLinkProvider from './get_link_provider.mjs';
4 | import getFoldProvider from './get_fold_provider.mjs';
5 | import getEditorChangeHandler from './get_editor_change_handler.mjs';
6 | import getGoBackCmd from './get_go_back_cmd.mjs';
7 | import getOpenCmd from './get_open_cmd.mjs';
8 |
9 |
10 | // Not exposed by default
11 | vscode.Uri.Utils = Utils;
12 |
13 |
14 | export async function activate(ctx) {
15 | const HISTORY_KEY = 'file-history';
16 | const LinkProvider = getLinkProvider(vscode);
17 | const FoldProvider = getFoldProvider(vscode);
18 | const onEditorChange = getEditorChangeHandler(ctx, HISTORY_KEY);
19 | const goBackCmd = getGoBackCmd(vscode, ctx, HISTORY_KEY);
20 | const openCmd = getOpenCmd(vscode);
21 |
22 | vscode.window.onDidChangeActiveTextEditor(onEditorChange);
23 | const regDocLinkProvider = vscode.languages.registerDocumentLinkProvider;
24 | ctx.subscriptions.push(regDocLinkProvider('xi', new LinkProvider()));
25 | const regFoldProvider = vscode.languages.registerFoldingRangeProvider;
26 | ctx.subscriptions.push(regFoldProvider('xi', new FoldProvider()));
27 | const regCmd = vscode.commands.registerCommand;
28 | ctx.subscriptions.push(regCmd('extension.xi.goBack', goBackCmd));
29 | ctx.subscriptions.push(regCmd('extension.xi.open', openCmd));
30 |
31 | // FIXME: not implemented for web context, need use cases.
32 | if (vscode.env.uiKind !== vscode.UIKind.Web ) {
33 | const loadedModule = await import('./get_lookup_cmd.mjs');
34 | const lookupCmd = loadedModule.default(vscode);
35 | ctx.subscriptions.push(regCmd('extension.xi.lookup', lookupCmd));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/get_editor_change_handler.mjs:
--------------------------------------------------------------------------------
1 | export default function getEditorChangeHandler(ctx, key) {
2 | return function(e) {
3 | if (!e) return;
4 | if (!e.document) return;
5 | const filePath = e.document.fileName;
6 | if (filePath.endsWith('.xi')) {
7 | let history = ctx.globalState.get(key);
8 | if (!Array.isArray(history)) history = [];
9 | if (history.length > 100) history = history.slice(-100);
10 | history.push(filePath);
11 | ctx.globalState.update(key, history);
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/get_fold_provider.mjs:
--------------------------------------------------------------------------------
1 | export default function getFoldProvider(vscode) {
2 |
3 |
4 | class FoldProvider {
5 |
6 |
7 | provideFoldingRanges(doc, context, token) {
8 | // return [vscode.FoldingRange(0, 10, vscode.FoldingRangeKind.Region)];
9 | return [];
10 | }
11 | }
12 |
13 |
14 | return FoldProvider;
15 | }
16 |
--------------------------------------------------------------------------------
/src/get_go_back_cmd.mjs:
--------------------------------------------------------------------------------
1 | export default function getGoBackCmf(vscode, ctx, key) {
2 | return function() {
3 | const history = ctx.globalState.get(key);
4 | if (Array.isArray(history) && history.length > 1) {
5 | // Current file.
6 | history.pop();
7 | // Previous file
8 | const filePath = history[history.length - 1];
9 | ctx.globalState.update(key, history);
10 | const uri = vscode.Uri.file(filePath);
11 | vscode.workspace.openTextDocument(uri).then(doc => {
12 | vscode.window.showTextDocument(doc);
13 | });
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/get_jump_to_anchor.mjs:
--------------------------------------------------------------------------------
1 | export default function getJumpToAnchor(vscode) {
2 |
3 | // From MDN
4 | function escapeRegExp(string) {
5 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
6 | }
7 |
8 |
9 | function jumpToPos(editor, idx) {
10 | const pos = editor.document.positionAt(idx);
11 | const range = new vscode.Range(pos, pos);
12 | editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
13 | const selection = new vscode.Selection(pos, pos);
14 | editor.selection = selection;
15 | }
16 |
17 |
18 | return function(anchor) {
19 | anchor = anchor.toLowerCase();
20 | const editor = vscode.window.activeTextEditor;
21 | if (!editor) return;
22 | let text = editor.document.getText().toLowerCase();
23 |
24 | // Special case, link to same document like [#foo]
25 | if (anchor.startsWith('#')) {
26 | const query = escapeRegExp(`[${anchor.slice(1)}#]`);
27 | const idx = text.search(query);
28 | if (!idx) return;
29 | jumpToPos(editor, idx);
30 | return;
31 | }
32 |
33 | let lastFoundIdx = null;
34 | let lastFoundSize = 0;
35 | // Handle nested anchor like [foo#bar#baz]
36 | for (const fragment of anchor.split('#')) {
37 | const query = (() => {
38 | const needle = escapeRegExp(fragment);
39 | //! Can't use '\s' since it matches '\n' in multiline mode.
40 | const link = `(\\[[^\\]]*\\])?`;
41 | const selflink = `(\\[\\])?`;
42 | // Type for headers that defines something that has a type:
43 | // @ property .
44 | // @(seq) typed-property .
45 | const type = `([^ \\t\\n]+[ \\t])?`;
46 | const begin = `[ \\t]*${type}${link}[ \\t]*`;
47 | const end = `${selflink}[ \\t]*${link}[ \\t]\\.`;
48 | const query = `^${begin}${needle}${end}$`;
49 | // No 'g', first match.
50 | return new RegExp(query, 'im');
51 | })();
52 |
53 | const match = text.match(query);
54 | // Break on first not found and use last found index, if any.
55 | if (!match) break;
56 |
57 | if (!lastFoundIdx) {
58 | lastFoundIdx = match.index;
59 | }
60 | else {
61 | // We are searching remainaing text, so it's a relative index.
62 | lastFoundIdx += lastFoundSize + match.index;
63 | }
64 |
65 | // Remaining text.
66 | lastFoundSize = match[0].length;
67 | text = text.slice(match.index + lastFoundSize);
68 |
69 | }
70 |
71 | if (lastFoundIdx) {
72 | jumpToPos(editor, lastFoundIdx);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/get_link_provider.mjs:
--------------------------------------------------------------------------------
1 | import * as tools from './tools.mjs';
2 |
3 |
4 | // Requires vscode.Uri.Utils to reference { Utils } from 'vscode-uri'
5 | export default function getLinkProvider(vscode) {
6 | const Utils = vscode.Uri.Utils;
7 |
8 |
9 | class LinkProvider {
10 |
11 |
12 | async provideDocumentLinks(doc, cancel) {
13 | const cfg = vscode.workspace.getConfiguration('xi');
14 | if (cfg.debug) {
15 | tools.debug(vscode, "LinkProvider().provideDocumentLinks()");
16 | const dirUri = Utils.dirname(vscode.Uri.file(doc.fileName));
17 | tools.debug(vscode, ` doc ${doc.fileName} dir ${dirUri.toString()}`);
18 | }
19 |
20 | const res = [];
21 | res.push(... await this._getWikiwordLinks(doc, cancel));
22 | if (cancel.isCancellationRequested) return [];
23 | res.push(... await this._getHeaderLinks(doc, cancel));
24 | if (cancel.isCancellationRequested) return [];
25 | return res;
26 | }
27 |
28 |
29 | // Give link name inside [], like "foo#bar" evaluates to vscode.Uri
30 | // or null.
31 | async _linkNameToVSCodeURI(doc, name) {
32 | if (name.startsWith('#')) {
33 | const anchor = name.slice(1).trim();
34 | if (anchor.length) {
35 | return vscode.Uri.parse(`command:extension.xi.open?${
36 | encodeURIComponent(JSON.stringify({
37 | // Without file it's not header anchor, but a wikiword.
38 | anchor,
39 | }))
40 | }`);
41 | }
42 | else {
43 | // [#] or #[] is meaningless
44 | return null;
45 | }
46 | }
47 | else if (name.match(/^[a-zA-Z_-]+:\/\//)) {
48 | return vscode.Uri.parse(name);
49 | }
50 | else {
51 | const [link, ...anchorSeq] = name.split('#');
52 | const anchor = anchorSeq.join('#');
53 | // Always use lowercase since different wikiwords can have
54 | // different writing depending on context, ex capitalized at
55 | // the sentence start.
56 | const fileName = `${link.replace(/ /g, '_')}.xi`.toLowerCase();
57 | const dirUri = Utils.dirname(vscode.Uri.file(doc.fileName));
58 | let fileUri = null;
59 | if (dirUri.path && dirUri.path.length > 0) {
60 | fileUri = Utils.joinPath(dirUri, fileName);
61 | } else {
62 | fileUri = vscode.Uri.file(fileName);
63 | }
64 |
65 | if (anchor) {
66 | return vscode.Uri.parse(`command:extension.xi.open?${
67 | encodeURIComponent(JSON.stringify({file: fileUri.path, anchor}))
68 | }`);
69 | }
70 | else {
71 | try {
72 | await vscode.workspace.fs.stat(fileUri);
73 | // Open existing file reusing current editor tab if possible
74 | return vscode.Uri.parse(`command:extension.xi.open?${
75 | encodeURIComponent(JSON.stringify({file: fileUri.path}))
76 | }`);
77 | } catch(e) {
78 | // If no anchor like [foo#bar] or foo#bar[] is specified, use
79 | // normal file // uri so VSCode will ask to create a file if
80 | // it does not exists. This will use a new editor tab.
81 | return fileUri;
82 | }
83 | }
84 | }
85 | }
86 |
87 |
88 | // Gather links for simple wikiwords like "[foo#bar]"
89 | async _getWikiwordLinks(doc, cancel) {
90 | const res = [];
91 | const text = doc.getText();
92 | const textLen = text.length
93 | const query = /\[[^ \]\r\n]\]|\[[^ \]\r\n][^\]\r\n]*[^ \]\r\n]\]/gm;
94 | while (true) {
95 | if (cancel.isCancellationRequested) return [];
96 | const match = query.exec(text);
97 | if (!match) break;
98 |
99 | // Skip '[' and ']', min length is 3 for wikiwords like '[v]'.
100 | const beginIdx = match.index + 1;
101 | const endIdx = match.index + match[0].length - 1;
102 | let lineBeginIdx = match.index;
103 | while (lineBeginIdx > 0 && text[lineBeginIdx - 1] !== "\n") {
104 | lineBeginIdx --;
105 | }
106 | let lineEndIdx = match.index;
107 | while (lineEndIdx < textLen && text[lineEndIdx + 1] !== "\n") {
108 | lineEndIdx += 1;
109 | }
110 | const name = text.substr(beginIdx, endIdx - beginIdx);
111 | // String from line start to link match.
112 | const prefix = text.substr(lineBeginIdx, match.index - lineBeginIdx);
113 | // Line containing the link
114 | const line = text.substr(lineBeginIdx, lineEndIdx - lineBeginIdx + 1);
115 |
116 | // Do not match links in code smaples like ` | [foo]`
117 | if (prefix.match(/^\s*\|\s+/)) continue;
118 | // Do not match links in code smaples like `. | [foo]`
119 | if (prefix.match(/^\s*\. \|\s+/)) continue;
120 | // Do not match links that contain '|', most probably it's
121 | // something like "|[| |]]|"
122 | if (name.includes('|')) continue;
123 |
124 | const charBefore = beginIdx > 1 ? text[beginIdx - 2] : null;
125 | const charAfter = endIdx < text.length - 1 ? text[endIdx + 1] : null;
126 | // Can be something like |[foo]| or |{lng:py} foo[bar]|
127 | if (line.includes('|')) {
128 | let markBeginIdx = null;
129 | let found = false;
130 | // Go from left to right and find all marked words and code
131 | // samples. They are started with pipe plus adjasted character
132 | // to the right and ended with pipe plus adjasted character
133 | // to the left, with any characters inbetween.
134 | for (let i = lineBeginIdx + 1; i <= lineEndIdx - 1; i ++) {
135 | const curChar = text[i];
136 | const prevChar = text[i - 1];
137 | const nextChar = text[i + 1];
138 | if (curChar != ' ' && prevChar == '|') {
139 | markBeginIdx = i;
140 | continue;
141 | }
142 | if (curChar != ' ' && nextChar == '|' && markBeginIdx) {
143 | const markEndIdx = i;
144 | // Link within a marked word or a code sample?
145 | if (beginIdx >= markBeginIdx && endIdx <= markEndIdx) {
146 | found = true;
147 | break;
148 | }
149 | markBeginIdx = null;
150 | }
151 | }
152 | // Do not match links inside marked words: `|foo| |bar| |[baz]|`
153 | // and inside code samples with meta info: `|{lng:js}[foo]|`
154 | if (found) continue;
155 | }
156 |
157 | //! Can't rely on pipe count: `||| [foo]`.
158 | // if (charBefore === '|' && charAfter === '|') continue;
159 |
160 | const uri = await this._linkNameToVSCodeURI(doc, name);
161 | if (uri) {
162 | const toPosition = (v) => doc.positionAt(v);
163 | const [begin, end] = [beginIdx, endIdx].map(toPosition);
164 | const range = new vscode.Range(begin, end);
165 | res.push(new vscode.DocumentLink(range, uri));
166 | }
167 | }
168 | return res;
169 | }
170 |
171 |
172 | // Gather links for header wikiwords like " [foo] bar baz[] [foo] ."
173 | // This also includes header anchor links like #todo[]
174 | async _getHeaderLinks(doc, cancel) {
175 | const res = [];
176 | const text = doc.getText();
177 | const query = (() => {
178 | const link = `\\[[^\\]]+\\]`;
179 | // Header can't end with space, ex 'foo [] .': '[]' or ' .' should
180 | // be adjasted to it.
181 | const header = `[^\\[\\r\\n]*[^ \\[\\r\\n]`;
182 | const terminator = `\\s\\.`;
183 | const begin = `\\s*(?:${link}\\s+)?`;
184 | const end = `\\[\\](?:\\s+${link})?${terminator}`;
185 | const query = `^(${begin})(${header})(${end})$`;
186 | return new RegExp(query, 'gm');
187 | })();
188 | while (true) {
189 | var match = query.exec(text);
190 | if (!match) break;
191 | if (cancel.isCancellationRequested) return [];
192 | // We have exactly 3 capture groups
193 | if (match.length !== 4) continue;
194 | // match[1] is 'begin' query part
195 | let beginIdx = match.index + match[1].length;
196 | // match[3] is 'end' query part
197 | let endIdx = match.index + match[0].length - match[3].length;
198 | const name = text.substr(beginIdx, endIdx - beginIdx);
199 |
200 | const uri = await this._linkNameToVSCodeURI(doc, name)
201 | if (uri) {
202 | const toPosition = (v) => doc.positionAt(v);
203 | const [begin, end] = [beginIdx, endIdx].map(toPosition);
204 | const range = new vscode.Range(begin, end);
205 | res.push(new vscode.DocumentLink(range, uri));
206 | }
207 | }
208 | return res;
209 | }
210 | }
211 |
212 | return LinkProvider;
213 | }
214 |
--------------------------------------------------------------------------------
/src/get_lookup_cmd.mjs:
--------------------------------------------------------------------------------
1 | import * as process from 'process';
2 | import * as util from 'util';
3 | import * as path from 'path';
4 | import * as fs from 'fs';
5 | import * as os from 'os';
6 | import * as tools from './tools.mjs';
7 | const p = util.promisify;
8 |
9 |
10 | export default function getLookupCmd(vscode) {
11 | return async () => {
12 | const cfg = vscode.workspace.getConfiguration('xi');
13 | let xiDir = cfg.lookupPath;
14 | // Something like "~/Documents/PowerShell/xi" on any OS.
15 | if (!xiDir) {
16 | return tools.error(vscode, tools.TXT_NO_LOOKUP_PATH);
17 | }
18 | xiDir = xiDir.replace(/^~/, `${os.homedir()}/`);
19 | xiDir = xiDir.replace(/[\/\\]/g, path.sep);
20 | xiDir = xiDir.replace(/\${env:([^}]*)}/g, (match, p1) => {
21 | return process.env[p1] || ""
22 | });
23 | try {
24 | const stat = await p(fs.stat)(xiDir);
25 | if (!stat.isDirectory()) {
26 | return tools.error(vscode, tools.TXT_NOT_DIR, xiDir);
27 | }
28 | }
29 | catch (e) {
30 | return tools.error(vscode, tools.TXT_NOT_DIR, xiDir);
31 | }
32 |
33 | let nameSeq = [];
34 | try {
35 | nameSeq = await p(fs.readdir)(xiDir);
36 | }
37 | catch (e) {
38 | return tools.error(vscode, tools.TXT_ERR_READ, xiDir);
39 | }
40 |
41 | nameSeq = nameSeq.filter(v => {
42 | if (v === '.git') return false;
43 | return true;
44 | });
45 | if (!nameSeq.length) return;
46 |
47 | const name = await vscode.window.showQuickPick(nameSeq);
48 | if (!name) return;
49 |
50 | const uri = vscode.Uri.file(path.join(xiDir, name));
51 | try {
52 | const doc = await vscode.workspace.openTextDocument(uri);
53 | await vscode.window.showTextDocument(doc);
54 | }
55 | catch (e) {
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/get_open_cmd.mjs:
--------------------------------------------------------------------------------
1 | import getJumpToAnchor from './get_jump_to_anchor.mjs';
2 |
3 |
4 | export default function getOpenCmd(vscode) {
5 | const jumpToAnchor = getJumpToAnchor(vscode);
6 |
7 |
8 | return function(argmap) {
9 | if (!argmap) return;
10 |
11 | const {file, anchor} = argmap;
12 | // Link like [#foo] to [#foo] anchor in same file.
13 | if (!file && anchor) {
14 | // jump to anchor like [foo#]
15 | jumpToAnchor(`#${anchor}`);
16 | }
17 | // Link like [foo] or [foo#bar].
18 | else {
19 | const uri = vscode.Uri.file(file);
20 | vscode.workspace.openTextDocument(uri).then(doc => {
21 | vscode.window.showTextDocument(doc).then(() => {
22 | if (!anchor) return;
23 | // Search headings like ` * [link] some header[] [another link] .`
24 | jumpToAnchor(anchor);
25 | });
26 | });
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/icon.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grigoryvp/vscode-language-xi/976d1bbb04f56bcf55b0c1f13a6773a8c73fbef7/src/icon.afdesign
--------------------------------------------------------------------------------
/src/tools.mjs:
--------------------------------------------------------------------------------
1 | export const TXT_NO_LOOKUP_PATH = `'xi.lookupPath' config not specified`;
2 | export const TXT_NOT_DIR = `'{0}' is not a directory`;
3 | export const TXT_ERR_READ = `Can't read '{0}'`;
4 |
5 |
6 | // Wait some time for VSCode to fully load before using this.
7 | export function debug(vscode, ...msgList) {
8 | if (!this._vscode) {
9 | this._vscode = vscode;
10 | this._channel = this._vscode.window.createOutputChannel("language-xi");
11 | }
12 | _channel.show();
13 | _channel.append(`${msgList.join(", ")}\n`);
14 | }
15 |
16 |
17 | // Display error message with interpolation.
18 | export function error(vscode, msg, ...valSeq) {
19 | if (!this._vscode) {
20 | this._vscode = vscode;
21 | this._channel = this._vscode.window.createOutputChannel("language-xi");
22 | }
23 | // error("{0} not found", "foo") => "foo not found"
24 | for (const [i, val] of Object.entries(valSeq)) {
25 | msg = msg.replace(`{${i}}`, val);
26 | }
27 | _channel.append(`${msg}\n`);
28 | _vscode.window.showErrorMessage(msg);
29 | }
30 |
--------------------------------------------------------------------------------
/src/xi-markdown-injection.tmLanguage.json:
--------------------------------------------------------------------------------
1 | {
2 | "comment": [
3 | "https://stackoverflow.com/a/76239666/69882"
4 | ],
5 | "fileTypes": [],
6 | "injectionSelector": "L:text.html.markdown",
7 | "patterns": [{
8 | "include": "#xi-code-block"
9 | }],
10 | "repository": {
11 | "xi-code-block": {
12 | "begin": "(^|\\G)(\\s*)(\\`{3,}|~{3,})\\s*(?i:(xi)(\\s+[^`~]*)?$)",
13 | "name": "markup.fenced_code.block.markdown",
14 | "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$",
15 | "beginCaptures": {
16 | "3": {
17 | "name": "punctuation.definition.markdown"
18 | },
19 | "4": {
20 | "name": "fenced_code.block.language.markdown"
21 | },
22 | "5": {
23 | "name": "fenced_code.block.language.attributes.markdown"
24 | }
25 | },
26 | "endCaptures": {
27 | "3": {
28 | "name": "punctuation.definition.markdown"
29 | }
30 | },
31 | "patterns": [{
32 | "begin": "(^|\\G)(\\s*)(.*)",
33 | "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)",
34 | "contentName": "meta.embedded.block.xi",
35 | "patterns": [{
36 | "include": "text.xi"
37 | }]
38 | }]
39 | }
40 | },
41 | "scopeName": "markdown.xi.codeblock"
42 | }
43 |
--------------------------------------------------------------------------------
/src/xi.tmLanguage.json:
--------------------------------------------------------------------------------
1 | {
2 | "comment": [
3 | "TODO: none yet"
4 | ],
5 | "patterns": [{
6 | "comment": "caption",
7 | "name": "beginning.punctuation.definition.list.markdown.xi",
8 | "match": "^[^ ].* @$",
9 | "captures": {"0": {"patterns": [{"include": "#header-end"}]}}
10 | },
11 |
12 | {
13 | "comment": "headers"
14 | }, {
15 | "comment": "h1, keyword, tag name",
16 | "name": "entity.name.function.xi",
17 | "match": "^[^ ].* \\.$",
18 | "captures": {"0": {"patterns": [{"include": "#header-end"}]}}
19 | }, {
20 | "comment": "h2, callable",
21 | "name": "entity.name.class.xi",
22 | "match": "^ {2}[^ ].* \\.$",
23 | "captures": {"0": {"patterns": [{"include": "#header-end"}]}}
24 | }, {
25 | "comment": "h3, property",
26 | "name": "constant.character.character-class.regexp.xi",
27 | "match": "^ {4}[^ ].* \\.$",
28 | "captures": {"0": {"patterns": [{"include": "#header-end"}]}}
29 | }, {
30 | "comment": "h4, type",
31 | "name": "constant.regexp.xi",
32 | "match": "^ {6}[^ ].* \\.$",
33 | "captures": {"0": {"patterns": [{"include": "#header-end"}]}}
34 | }, {
35 | "comment": "h5, enum, preprocessor, constant",
36 | "name": "keyword.control.xi",
37 | "match": "^ {8}[^ ].* \\.$",
38 | "captures": {"0": {"patterns": [{"include": "#header-end"}]}}
39 | }, {
40 | "comment": "h6, number",
41 | "name": "invalid.xi",
42 | "match": "^ {10}[^ ].* \\.$",
43 | "captures": {"0": {"patterns": [{"include": "#header-end"}]}}
44 | },
45 |
46 | {
47 | "comment": "paragraph with code block start",
48 | "match": "^ *([\\.\\?\\!\\*\\#\\+\\-] )*\\|($| .+$|\\{[^\\}]*\\}$|\\{[^\\}]*\\} .+$)",
49 | "captures": {
50 | "0": {"patterns": [{"include": "#code-block"}]},
51 | "1": {"name": "constant.other.color.rgb-value.xi"}
52 | }
53 | },
54 |
55 | {
56 | "comment": "paragraph start",
57 | "match": "^ *([\\.\\?\\!\\*\\#\\+\\-]) (.+)",
58 | "captures": {
59 | "1": {"name": "constant.other.color.rgb-value.xi"},
60 | "2": {
61 | "patterns": [{
62 | "include": "#http-link"
63 | }, {
64 | "comment": "before 'special', ex |{lng:foo}bar| is code",
65 | "include": "#code-inline"
66 | }, {
67 | "include": "#wikiword"
68 | }, {
69 | "include": "#accent"
70 | }, {
71 | "include": "#parameter"
72 | }, {
73 | "include": "#special"
74 | }]
75 | }
76 | }
77 | },
78 |
79 | {
80 | "comment": "[http://foo|",
81 | "include": "#http-link"
82 | },
83 |
84 | {
85 | "comment": "block code sample, spaces followed by '|' and space",
86 | "match": "^ *\\|($| .+$|\\{[^\\}]*\\} .+$)",
87 | "captures": {
88 | "0": {"patterns": [{"include": "#code-block"}]}
89 | }
90 | },
91 |
92 | {
93 | "comment": "inline code sample, |{descr}code|; before 'special'",
94 | "include": "#code-inline"
95 | },
96 |
97 | {
98 | "comment": "text between '[' and ']' without adjasted spaces",
99 | "include": "#wikiword"
100 | },
101 |
102 | {
103 | "comment": "text between '`' without adjasted spaces",
104 | "include": "#accent"
105 | },
106 |
107 | {
108 | "comment": "text between {} prefixed with a single letter and a space",
109 | "include": "#parameter"
110 | },
111 |
112 | {
113 | "comment": "text between '|' and '|' without adjasted spaces",
114 | "include": "#special"
115 | }],
116 |
117 | "repository": {
118 | "header-end": {
119 | "patterns": [{
120 | "comment": "plain header, ex 'foo .'",
121 | "match": "[^ ] (\\.|@)$",
122 | "captures": {
123 | "1": {"name": "punctuation.definition.tag.xi"}
124 | }
125 | },{
126 | "comment": "header that is itself a link, ex 'foo[] .', no spaces",
127 | "match": "[^ ](\\[\\]) (\\.|@)$",
128 | "captures": {
129 | "1": {"name": "constant.character.xi"},
130 | "2": {"name": "punctuation.definition.tag.xi"}
131 | }
132 | }, {
133 | "comment": "header with link, ex 'foo [bar] .', space separated",
134 | "match": "[^ ] +(\\[.+\\]) (\\.|@)$",
135 | "captures": {
136 | "1": {
137 | "name": "punctuation.definition.tag.xi",
138 | "patterns": [{
139 | "match": "\\[(.+)\\]",
140 | "captures": {
141 | "1": {"name": "constant.character.xi"}
142 | }
143 | }]
144 | },
145 | "2": {"name": "punctuation.definition.tag.xi"}
146 | }
147 | }]
148 | },
149 |
150 | "special": {
151 | "patterns": [{
152 | "comment": "text like ||foo||",
153 | "match": "\\|([^ ]+)\\|",
154 | "name": "punctuation.definition.tag.xi",
155 | "captures": {"1": {"name": "beginning.punctuation.definition.quote.markdown.xi"}}
156 | }, {
157 | "comment": "text like |foo bar baz|",
158 | "match": "\\|([^ \\|][^\\|]*[^ \\|])\\|",
159 | "name": "punctuation.definition.tag.xi",
160 | "captures": {"1": {"name": "beginning.punctuation.definition.quote.markdown.xi"}}
161 | }]
162 | },
163 |
164 | "accent": {
165 | "patterns": [{
166 | "comment": "`foo bar baz`",
167 | "match": "`([^ `]?)`|`([^ `][^`]*[^ `])`",
168 | "name": "punctuation.definition.tag.xi",
169 | "captures": {
170 | "1": {"name": "accent.xi"},
171 | "2": {"name": "accent.xi"}
172 | }
173 | }]
174 | },
175 |
176 | "parameter": {
177 | "patterns": [{
178 | "comment": "'{i}",
179 | "match": "(\\{)([^ \\}])(\\})",
180 | "name": "constant.other.color.rgb-value.xi",
181 | "captures": {
182 | "1": {"name": "punctuation.definition.tag.xi"},
183 | "2": {"name": "constant.character.character-class.regexp.xi"},
184 | "3": {"name": "punctuation.definition.tag.xi"}
185 | }
186 | }, {
187 | "comment": "'{i foo}",
188 | "match": "(\\{)([^ \\}]) [^\\}]+(\\})",
189 | "name": "constant.other.color.rgb-value.xi",
190 | "captures": {
191 | "1": {"name": "punctuation.definition.tag.xi"},
192 | "2": {"name": "constant.character.character-class.regexp.xi"},
193 | "3": {"name": "punctuation.definition.tag.xi"}
194 | }
195 | }, {
196 | "comment": "{i(type)}",
197 | "match": "(\\{)([^ \\}])(\\()([^\\)]+)(\\))(\\})",
198 | "name": "constant.other.color.rgb-value.xi",
199 | "captures": {
200 | "1": {"name": "punctuation.definition.tag.xi"},
201 | "2": {"name": "constant.character.character-class.regexp.xi"},
202 | "3": {"name": "punctuation.definition.tag.xi"},
203 | "4": {"name": "beginning.punctuation.definition.quote.markdown.xi"},
204 | "5": {"name": "punctuation.definition.tag.xi"},
205 | "6": {"name": "punctuation.definition.tag.xi"}
206 | }
207 | }, {
208 | "comment": "'{i(type) foo}",
209 | "match": "(\\{)([^ \\}])(\\()([^\\)]+)(\\)) [^\\}]+(\\})",
210 | "name": "constant.other.color.rgb-value.xi",
211 | "captures": {
212 | "1": {"name": "punctuation.definition.tag.xi"},
213 | "2": {"name": "constant.character.character-class.regexp.xi"},
214 | "3": {"name": "punctuation.definition.tag.xi"},
215 | "4": {"name": "beginning.punctuation.definition.quote.markdown.xi"},
216 | "5": {"name": "punctuation.definition.tag.xi"},
217 | "6": {"name": "punctuation.definition.tag.xi"}
218 | }
219 | }, {
220 | "comment": "Most generic case, last to match: {foo}, {Foo} etc",
221 | "match": "(\\{)[^ \\}][^\\}]+(\\})",
222 | "name": "constant.other.color.rgb-value.xi",
223 | "captures": {
224 | "1": {"name": "punctuation.definition.tag.xi"},
225 | "2": {"name": "punctuation.definition.tag.xi"}
226 | }
227 | }]
228 | },
229 |
230 | "wikiword": {
231 | "patterns": [{
232 | "comment": "[#link-like]",
233 | "match": "\\[(#[^\\]]*)\\]",
234 | "name": "punctuation.definition.tag.xi",
235 | "captures": {
236 | "1": {"name": "constant.character.xi"}
237 | }
238 | }, {
239 | "comment": "[https://link-like]",
240 | "match": "\\[([^:\\]]*://[^\\]]*)\\]",
241 | "name": "punctuation.definition.tag.xi",
242 | "captures": {
243 | "1": {"name": "constant.character.xi"},
244 | "2": {"name": "constant.character.xi"}
245 | }
246 | }, {
247 | "comment": "[foo bar baz]",
248 | "match": "\\[([^ \\]]?)\\]|\\[([^ \\]][^\\]]*[^ \\]])\\]",
249 | "name": "punctuation.definition.tag.xi",
250 | "captures": {
251 | "1": {"name": "wikiword.xi"},
252 | "2": {"name": "wikiword.xi"}
253 | }
254 | }]
255 | },
256 |
257 | "http-link": {
258 | "patterns": [{
259 | "comment": "|http://foo|",
260 | "match": "\\|(http://[^\\|]+)\\|",
261 | "name": "punctuation.definition.tag.xi",
262 | "captures": {"1": {"name": "constant.character.xi"}}
263 | }, {
264 | "comment": "|https://foo|",
265 | "match": "\\|(https://[^\\|]+)\\|",
266 | "name": "punctuation.definition.tag.xi",
267 | "captures": {"1": {"name": "constant.character.xi"}}
268 | }]
269 | },
270 |
271 | "code-block": {
272 | "comment": "block code sample, spaces followed by '|' and space",
273 | "patterns": [{
274 | "match": "^ *([\\.\\?\\!\\*\\#\\+\\-] )*(\\|)(\\{[^\\}]*\\})*(.*)$",
275 | "captures": {
276 | "1": {"name": "constant.other.color.rgb-value.xi"},
277 | "2": {"name": "punctuation.definition.tag.xi"},
278 | "3": {
279 | "patterns": [{
280 | "match": "\\{[^\\}]*\\}",
281 | "name": "punctuation.definition.tag.xi"
282 | }]
283 | },
284 | "4": {"name": "constant.other.color.rgb-value.xi"}
285 | }
286 | }]
287 | },
288 |
289 | "code-inline": {
290 | "patterns": [{
291 | "comment": "inline code sample, |{descr}foo|bar|",
292 | "match": "\\|\\{[^\\}]+\\}([^ ]+)\\|",
293 | "name": "punctuation.definition.tag.xi",
294 | "captures": {
295 | "1": {"name": "constant.other.color.rgb-value.xi"}
296 | }
297 | }, {
298 | "comment": "inline code sample, |{descr}foo bar baz|",
299 | "match": "\\|\\{[^\\}]+\\}([^\\|]+)\\|",
300 | "name": "punctuation.definition.tag.xi",
301 | "captures": {
302 | "1": {"name": "constant.other.color.rgb-value.xi"}
303 | }
304 | }]
305 | }
306 | },
307 |
308 | "scopeName": "text.xi"
309 | }
310 |
--------------------------------------------------------------------------------
/src/xi_parser.mjs:
--------------------------------------------------------------------------------
1 | export function parse(text, cancel) {
2 | return [];
3 | }
4 |
5 |
6 | export class Token {
7 | // Indentation followed by dot and space: marks paragraph begin.
8 | static BEGIN_MARK = 2;
9 | // Space followed by dot and EOL: marks heading end.
10 | static END_MARK = 3;
11 | // Anything else.
12 | static TEXT = 4;
13 |
14 |
15 | constructor() {
16 | this.type = null;
17 | this.pos = 0;
18 | this.indent = 0;
19 | this.text = "";
20 | }
21 | }
22 |
23 |
24 | // Parse text into tokens which has position within text. Tokens before
25 | // and after of the current one are analyzed in order to get meaning
26 | // from the grammar. Tokens should be organized a way so no knowledge of
27 | // previous or next token is required to determine current token.
28 | export function tokenize(text, cancel) {
29 | const STR_BEGIN = 2;
30 | const STR_INDENT = 3;
31 | const BEGIN_MARK_WAIT_SPACE = 4;
32 | const END_MARK_WAIT_DOT = 5;
33 | const END_MARK_WAIT_EOL = 6;
34 | const TEXT = 7;
35 | const EOF = null;
36 | let char = "";
37 | let mode = STR_BEGIN;
38 | let pos = -1;
39 | let acc = "";
40 | let token = null;
41 | let tokens = [];
42 |
43 | while(pos < text.length && !cancel.isCancellationRequested) {
44 | pos += 1;
45 | if (pos < text.length) {
46 | char = text[pos];
47 | }
48 | else {
49 | char = EOF;
50 | }
51 | switch(char) {
52 | case " ":
53 | acc += char;
54 | switch(mode) {
55 | case STR_BEGIN:
56 | case STR_INDENT:
57 | // If string starts with spaces it can be a paragraph indent
58 | mode = STR_INDENT;
59 | break;
60 | case BEGIN_MARK_WAIT_SPACE:
61 | token = new Token();
62 | token.text = acc;
63 | token.pos = pos - (acc.length - 1);
64 | token.type = Token.BEGIN_MARK;
65 | tokens.push(token);
66 | acc = "";
67 | mode = TEXT;
68 | break;
69 | case TEXT:
70 | mode = END_MARK_WAIT_DOT;
71 | break;
72 | default:
73 | mode = TEXT;
74 | break;
75 | }
76 | break;
77 | case ".":
78 | acc += char;
79 | switch(mode) {
80 | case STR_INDENT:
81 | const indentLen = acc.length - 1;
82 | if (indentLen % 2 == 0) {
83 | mode = BEGIN_MARK_WAIT_SPACE;
84 | }
85 | else {
86 | mode = TEXT;
87 | }
88 | break;
89 | case END_MARK_WAIT_DOT:
90 | mode = END_MARK_WAIT_EOL;
91 | break;
92 | default:
93 | mode = TEXT;
94 | break;
95 | }
96 | break;
97 | case "\r":
98 | case "\n":
99 | case EOF:
100 | switch(mode) {
101 | case END_MARK_WAIT_EOL:
102 | console.assert(acc.length > 2);
103 | const endMarkLen = 2;
104 | token = new Token();
105 | token.text = acc.substr(0, acc.length - endMarkLen);
106 | const eolPosOffset = 1;
107 | token.pos = pos - eolPosOffset - (acc.length - 1);
108 | token.type = Token.TEXT;
109 | tokens.push(token);
110 | token = new Token();
111 | token.text = " .";
112 | token.pos = pos - eolPosOffset - 1;
113 | token.type = Token.END_MARK;
114 | tokens.push(token);
115 | acc = "";
116 | mode = TEXT;
117 | break;
118 | default:
119 | if (acc.length) {
120 | token = new Token();
121 | token.text = acc;
122 | const eolPosOffset = 1;
123 | token.pos = pos - eolPosOffset - (acc.length - 1);
124 | token.type = Token.TEXT;
125 | tokens.push(token);
126 | }
127 | mode = STR_BEGIN;
128 | acc = "";
129 | break;
130 | }
131 | default:
132 | mode = TEXT;
133 | acc += char;
134 | break;
135 | }
136 | }
137 |
138 | return tokens;
139 | }
140 |
--------------------------------------------------------------------------------
/test/editor_change_handler.mjs:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai';
2 | const expect = chai.expect;
3 | import getHandler from './../src/get_editor_change_handler.mjs';
4 |
5 |
6 | describe("'active editor changed' handler", () => {
7 | const state = {};
8 | const ctx = {
9 | globalState: {
10 | get: (key) => state[key],
11 | update: (key, val) => state[key] = val,
12 | },
13 | document: {
14 | fileName: 'foo.xi'
15 | }
16 | };
17 |
18 |
19 | it("adds document to history", () => {
20 | const handler = getHandler(ctx, 'key');
21 | handler(ctx);
22 | expect(state).deep.includes({key: ['foo.xi']});
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/fold_provider.mjs:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai';
2 | const expect = chai.expect;
3 | import getFoldProvider from './../src/get_fold_provider.mjs';
4 |
5 |
6 | describe("FoldProvider class", () => {
7 |
8 | const vscode = {
9 | FoldingRange: (start, end, kind) => {start, end, kind},
10 | FoldingRangeKind: {
11 | Region: 3,
12 | },
13 | };
14 |
15 |
16 | it("can be instantiated", () => {
17 | const FoldProvider = getFoldProvider(vscode);
18 | const inst = new FoldProvider();
19 | expect(inst).to.be.ok;
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/go_back_cmd.mjs:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai';
2 | const expect = chai.expect;
3 | import getCmd from './../src/get_go_back_cmd.mjs';
4 |
5 |
6 | describe("'go back' command", () => {
7 | const state = {key: ['foo.xi', 'bar.xi']};
8 | const vscode = {
9 | Uri: {
10 | file: (path) => ({path}),
11 | parse: (text) => ({text}),
12 | },
13 | workspace: {
14 | openTextDocument: (uri) => new Promise((resolve) => resolve({uri})),
15 | },
16 | window: {
17 | showTextDocument: (doc) => {},
18 | },
19 | };
20 | const ctx = {
21 | globalState: {
22 | get: (key) => state[key],
23 | update: (key, val) => state[key] = val,
24 | },
25 | document: {
26 | fileName: 'foo.xi'
27 | }
28 | };
29 |
30 |
31 | it("gets document from history", () => {
32 | const cmd = getCmd(vscode, ctx, 'key');
33 | vscode.window.showTextDocument = (doc) => {
34 | expect(doc).deep.includes({uri: {path: 'foo.xi'}});
35 | };
36 | cmd(ctx);
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/test/jump_test.xi:
--------------------------------------------------------------------------------
1 | jump test @
2 |
3 | . [test].
4 | . [does not exist].
5 |
--------------------------------------------------------------------------------
/test/jump_to_anchor.mjs:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai';
2 | const expect = chai.expect;
3 | import getJumpToAnchor from './../src/get_jump_to_anchor.mjs';
4 |
5 |
6 | describe("jump to anchor", () => {
7 | const vscode = {
8 | Range: function(...v) { [this.begin, this.end] = [...v]; },
9 | Selection: function(...v) { [this.begin, this.end] = [...v]; },
10 | window: {
11 | activeTextEditor: {
12 | document: {
13 | getText: () => '',
14 | positionAt: (v) => v,
15 | },
16 | revealRange: () => {},
17 | }
18 | },
19 | TextEditorRevealType: {
20 | InCenter: 1,
21 | },
22 | };
23 |
24 |
25 | it("jumps to anchor", () => {
26 | const jumpToAnchor = getJumpToAnchor(vscode);
27 | vscode.window.activeTextEditor.document.getText = () => {
28 | return `\nfoo .`;
29 | }
30 | vscode.window.activeTextEditor.revealRange = (range) => {
31 | expect(range).deep.includes({begin: 1, end: 1});
32 | }
33 | jumpToAnchor('foo');
34 | });
35 |
36 |
37 | it("jumps to self document anchor", () => {
38 | const jumpToAnchor = getJumpToAnchor(vscode);
39 | vscode.window.activeTextEditor.document.getText = () => {
40 | return `\n[foo#]`;
41 | }
42 | vscode.window.activeTextEditor.revealRange = (range) => {
43 | expect(range).deep.includes({begin: 1, end: 1});
44 | }
45 | jumpToAnchor('#foo');
46 | });
47 |
48 |
49 | it("jumps to nested anchor", () => {
50 | const jumpToAnchor = getJumpToAnchor(vscode);
51 | vscode.window.activeTextEditor.document.getText = () => {
52 | return `\nfoo .\n bar .`;
53 | }
54 | vscode.window.activeTextEditor.revealRange = (range) => {
55 | expect(range).deep.includes({begin: 7, end: 7});
56 | }
57 | jumpToAnchor('foo#bar');
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/test/link_provider.mjs:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai';
2 | const expect = chai.expect;
3 | import getLinkProvider from './../src/get_link_provider.mjs';
4 |
5 |
6 | describe("LinkProvider class", async () => {
7 |
8 | const vscode = {
9 | Range: function(...v) { [this.begin, this.end] = [...v]; },
10 | DocumentLink: function(...v) { [this.range, this.uri] = [...v]; },
11 | Uri: {
12 | file: (path) => ({path}),
13 | parse: (text) => ({text}),
14 | Utils: {
15 | dirname: (path) => "",
16 | joinPath: (...items) => items.filter(v => v).join("/")
17 | }
18 | },
19 | workspace: {
20 | getConfiguration: () => {
21 | return {
22 | debug: false,
23 | };
24 | },
25 | }
26 | };
27 |
28 | const doc = {
29 | getText: () => '',
30 | fileName: 'foo.xi',
31 | positionAt: (v) => v,
32 | };
33 |
34 | const cancel = {};
35 |
36 |
37 | it("can be instantiated", () => {
38 | const LinkProvider = getLinkProvider(vscode);
39 | const inst = new LinkProvider();
40 | expect(inst).to.be.ok;
41 | });
42 |
43 |
44 | it("matches simple link", async () => {
45 | const LinkProvider = getLinkProvider(vscode);
46 | const inst = new LinkProvider();
47 | doc.getText = () => "[a]";
48 | const ret = await inst.provideDocumentLinks(doc, cancel);
49 | expect(ret).to.have.lengthOf(1);
50 | const link = ret[0];
51 | expect(link).deep.includes({range: {begin: 1, end: 2}});
52 | expect(link).deep.includes({uri: {path: "a.xi"}});
53 | });
54 |
55 |
56 | it("lowercase file names for links", async () => {
57 | const LinkProvider = getLinkProvider(vscode);
58 | const inst = new LinkProvider();
59 | doc.getText = () => "[Ab]";
60 | const ret = await inst.provideDocumentLinks(doc, cancel);
61 | expect(ret).to.have.lengthOf(1);
62 | const link = ret[0];
63 | expect(link).deep.includes({range: {begin: 1, end: 3}});
64 | expect(link).deep.includes({uri: {path: "ab.xi"}});
65 | });
66 |
67 |
68 | it("matches simple header link", async () => {
69 | const LinkProvider = getLinkProvider(vscode);
70 | const inst = new LinkProvider();
71 | doc.getText = () => "foo[] .";
72 | const ret = await inst.provideDocumentLinks(doc, cancel);
73 | expect(ret).to.have.lengthOf(1);
74 | const link = ret[0];
75 | expect(link).deep.includes({range: {begin: 0, end: 3}});
76 | });
77 |
78 |
79 | it("matches simple header link with offset", async () => {
80 | const LinkProvider = getLinkProvider(vscode);
81 | const inst = new LinkProvider();
82 | doc.getText = () => " foo[] .";
83 | const ret = await inst.provideDocumentLinks(doc, cancel);
84 | expect(ret).to.have.lengthOf(1);
85 | const link = ret[0];
86 | expect(link).deep.includes({range: {begin: 2, end: 5}});
87 | });
88 |
89 |
90 | it("matches header link for wikiword at start", async () => {
91 | const LinkProvider = getLinkProvider(vscode);
92 | const inst = new LinkProvider();
93 | doc.getText = () => "[foo] bar[] .";
94 | const ret = await inst.provideDocumentLinks(doc, cancel);
95 | expect(ret).to.have.lengthOf(2);
96 | const link = ret[1];
97 | expect(link).deep.includes({range: {begin: 6, end: 9}});
98 | });
99 |
100 |
101 | it("matches header link for wikiword at end", async () => {
102 | const LinkProvider = getLinkProvider(vscode);
103 | const inst = new LinkProvider();
104 | doc.getText = () => "foo[] [bar] .";
105 | const ret = await inst.provideDocumentLinks(doc, cancel);
106 | expect(ret).to.have.lengthOf(2);
107 | const link = ret[1];
108 | expect(link).deep.includes({range: {begin: 0, end: 3}});
109 | });
110 |
111 |
112 | it("matches header link to anchor", async () => {
113 | const LinkProvider = getLinkProvider(vscode);
114 | const inst = new LinkProvider();
115 | doc.getText = () => "#foo[] .";
116 | const ret = await inst.provideDocumentLinks(doc, cancel);
117 | expect(ret).to.have.lengthOf(1);
118 | const link = ret[0];
119 | expect(link).deep.includes({range: {begin: 0, end: 4}});
120 | const prefix = 'command:extension.xi.open?';
121 | const text = prefix + encodeURIComponent(JSON.stringify({
122 | anchor: "foo",
123 | }));
124 | expect(link).deep.includes({uri: {text}});
125 | });
126 |
127 |
128 | it("matches header link to url", async () => {
129 | const LinkProvider = getLinkProvider(vscode);
130 | const inst = new LinkProvider();
131 | doc.getText = () => "http://foo[] .";
132 | const ret = await inst.provideDocumentLinks(doc, cancel);
133 | expect(ret).to.have.lengthOf(1);
134 | const link = ret[0];
135 | expect(link).deep.includes({range: {begin: 0, end: 10}});
136 | expect(link).deep.includes({uri: {text: 'http://foo'}});
137 | });
138 |
139 |
140 | it("not matches inside single line code sample", async () => {
141 | const LinkProvider = getLinkProvider(vscode);
142 | const inst = new LinkProvider();
143 | doc.getText = () => "|{lng}[a]|";
144 | const ret = await inst.provideDocumentLinks(doc, cancel);
145 | expect(ret).to.have.lengthOf(0);
146 | });
147 |
148 |
149 | it("not matches inside multiline code sample", async () => {
150 | const LinkProvider = getLinkProvider(vscode);
151 | const inst = new LinkProvider();
152 | doc.getText = () => " | foo [a]";
153 | const ret = await inst.provideDocumentLinks(doc, cancel);
154 | expect(ret).to.have.lengthOf(0);
155 | });
156 |
157 |
158 | it("not matches inside paragraph code sample", async () => {
159 | const LinkProvider = getLinkProvider(vscode);
160 | const inst = new LinkProvider();
161 | doc.getText = () => ". | foo [a]";
162 | const ret = await inst.provideDocumentLinks(doc, cancel);
163 | expect(ret).to.have.lengthOf(0);
164 | });
165 |
166 |
167 | it("not matches inside marked text", async () => {
168 | const LinkProvider = getLinkProvider(vscode);
169 | const inst = new LinkProvider();
170 | doc.getText = () => "|foo| |bar| |[baz]|";
171 | let ret = await inst.provideDocumentLinks(doc, cancel);
172 | expect(ret).to.have.lengthOf(0);
173 |
174 | // Wrong number of pipes before link.
175 | doc.getText = () => "||a| [b]";
176 | ret = await inst.provideDocumentLinks(doc, cancel);
177 | expect(ret).to.have.lengthOf(1);
178 | const link = ret[0];
179 | expect(link).deep.includes({range: {begin: 6, end: 7}});
180 | });
181 |
182 |
183 | it("matches link with anchor", async () => {
184 | const LinkProvider = getLinkProvider(vscode);
185 | const inst = new LinkProvider();
186 | doc.getText = () => "[a#b]";
187 | const ret = await inst.provideDocumentLinks(doc, cancel);
188 | expect(ret).to.have.lengthOf(1);
189 | const link = ret[0];
190 | expect(link).deep.includes({range: {begin: 1, end: 4}});
191 | const prefix = 'command:extension.xi.open?';
192 | const text = prefix + encodeURIComponent(JSON.stringify({
193 | file: "a.xi",
194 | anchor: "b",
195 | }));
196 | expect(link).deep.includes({uri: {text}});
197 | });
198 |
199 |
200 | it("matches link with nested anchor", async () => {
201 | const LinkProvider = getLinkProvider(vscode);
202 | const inst = new LinkProvider();
203 | doc.getText = () => "[a#b#c]";
204 | const ret = await inst.provideDocumentLinks(doc, cancel);
205 | expect(ret).to.have.lengthOf(1);
206 | const link = ret[0];
207 | expect(link).deep.includes({range: {begin: 1, end: 6}});
208 | const prefix = 'command:extension.xi.open?';
209 | const text = prefix + encodeURIComponent(JSON.stringify({
210 | file: "a.xi",
211 | anchor: "b#c",
212 | }));
213 | expect(link).deep.includes({uri: {text}});
214 | });
215 |
216 |
217 | it("matches link to anchor", async () => {
218 | const LinkProvider = getLinkProvider(vscode);
219 | const inst = new LinkProvider();
220 | doc.getText = () => "[#b]";
221 | const ret = await inst.provideDocumentLinks(doc, cancel);
222 | expect(ret).to.have.lengthOf(1);
223 | const link = ret[0];
224 | expect(link).deep.includes({range: {begin: 1, end: 3}});
225 | const prefix = 'command:extension.xi.open?';
226 | const text = prefix + encodeURIComponent(JSON.stringify({
227 | anchor: "b"
228 | }));
229 | expect(link).deep.includes({uri: {text}});
230 | });
231 |
232 |
233 | it("matches http link", async () => {
234 | const LinkProvider = getLinkProvider(vscode);
235 | const inst = new LinkProvider();
236 | doc.getText = () => "[http://foo]";
237 | const ret = await inst.provideDocumentLinks(doc, cancel);
238 | expect(ret).to.have.lengthOf(1);
239 | const link = ret[0];
240 | expect(link).deep.includes({range: {begin: 1, end: 11}});
241 | expect(link).deep.includes({uri: {text: 'http://foo'}});
242 | });
243 |
244 | it("matches link anchors with slashes", async () => {
245 | const LinkProvider = getLinkProvider(vscode);
246 | const inst = new LinkProvider();
247 | doc.getText = () => "[a#b/#c/]";
248 | const ret = await inst.provideDocumentLinks(doc, cancel);
249 | expect(ret).to.have.lengthOf(1);
250 | const link = ret[0];
251 | expect(link).deep.includes({range: {begin: 1, end: 8}});
252 | const prefix = 'command:extension.xi.open?';
253 | const text = prefix + encodeURIComponent(JSON.stringify({
254 | file: "a.xi",
255 | anchor: "b/#c/",
256 | }));
257 | expect(link).deep.includes({uri: {text}});
258 | });
259 |
260 | it("not matches between marked text", async () => {
261 | const LinkProvider = getLinkProvider(vscode);
262 | const inst = new LinkProvider();
263 | doc.getText = () => "|[| |]]|";
264 | const ret = await inst.provideDocumentLinks(doc, cancel);
265 | expect(ret).to.have.lengthOf(0);
266 | });
267 | });
268 |
--------------------------------------------------------------------------------
/test/open_cmd.mjs:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai';
2 | const expect = chai.expect;
3 | import getCmd from './../src/get_open_cmd.mjs';
4 |
5 |
6 | describe("'open' command", () => {
7 | const vscode = {
8 | Uri: {
9 | file: (path) => ({path}),
10 | },
11 | workspace: {
12 | openTextDocument: (uri) => new Promise((resolve) => resolve({uri})),
13 | },
14 | window: {
15 | showTextDocument: (doc) => new Promise((resolve) => resolve()),
16 | },
17 | };
18 |
19 |
20 | it("opens a link", () => {
21 | const cmd = getCmd(vscode);
22 | vscode.window.showTextDocument = (doc) => {
23 | expect(doc).deep.includes({uri: {path: 'foo.xi'}});
24 | return new Promise((resolve) => resolve());
25 | }
26 | cmd({file: 'foo.xi'});
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/test/test.md:
--------------------------------------------------------------------------------
1 | ```python
2 | a = 1 # comment
3 | ```
4 | ```xi
5 | foo .
6 | bar .
7 | ```
8 |
--------------------------------------------------------------------------------
/test/test.xi:
--------------------------------------------------------------------------------
1 | xi test @
2 |
3 | Todo .
4 | #todo[] .
5 | # Links are not working in this test.
6 | # '#' list symbol is not working.
7 |
8 | . Text with {i input param}, {- cli param}, {i(typed) param}, {i} single
9 | param, single {i(typed)} param, {param} without i/o modifier and
10 | {Upper-case param}.
11 |
12 | |||
13 | [http://foo]
14 | http://foo[] .
15 | [https://foo]
16 | ||| text
17 | text |||
18 | text ||| text
19 | ||special||
20 | text ||special||
21 | ||special|| text
22 | text ||special|| text
23 | |special with spaces|
24 | text |special with spaces| text
25 | text | text
26 | text | text |
27 | text | text | text
28 | text |{lng:foo}not a special but language| text
29 | . text |{lng:foo}not a special but language| text
30 | . |{}special|.
31 | . |{}| this is special.
32 | . |||
33 | . ||| text
34 | . ||special||
35 | . ||special|| text
36 | . text |||
37 | . text ||| text
38 | . text ||special||
39 | . text ||special|| text
40 | . [http://foo]
41 | . [https://foo]
42 | | code
43 | |{Lng:foo} code
44 | | {lng:foo} code
45 | | code
46 | . | code
47 | . |{lng:foo} code
48 | |{Lng:foo} code
49 | |{lng:foo}
50 | |
51 | . | {lng:foo} code
52 | . |not a code
53 | ! | code
54 | ? | code
55 | . text | not a code
56 | [0.0...1.0) range, not a link.
57 | . |{}| non-text item (no meta).
58 | . |{| and |}| two non-text items.
59 | . |{x}| Non-text item, not a language specifier.
60 | . |{x}|: Non-text item and |:|.
61 | |{lng:py}a = 1| inline code with spaces, one per line, no '|'.
62 | |{lng:py}a=1| inline code without spaces, |{lng:py}a=1|2|.
63 | . |{ng:py}a = 1|: inline code after paragraph.
64 | . |[notlink]|.
65 | . |{lng:js}[notlink]|.
66 | . |{lng:js}
67 | | [notlink]
68 | . | [notlink]
69 | [jump test]
70 | [c] - single wikiword.
71 | kh1 .
72 | h1 link[] .
73 | h1 local link [#1] .
74 | h1 wikiword link [wikiword] .
75 | [wikiword] h1 wikiword link .
76 | [wikiword] h1 wikiword link [wikiword] .
77 | [wikiword] h1 wikiword link[] [wikiword] .
78 | . Paragraph.
79 | h2 .
80 | h2 link[] .
81 | h2 local link [#1] .
82 | h2 wikiword link [wikiword] .
83 | . Paragraph.
84 | h3 .
85 | h3 link[] .
86 | h3 local link [#1] .
87 | h3 wikiword link [wikiword] .
88 | . Paragraph.
89 | h4 .
90 | h4 link[] .
91 | h4 local link [#1] .
92 | h4 wikiword link [wikiword] .
93 | . Paragraph.
94 | h5 .
95 | h5 link[] .
96 | h5 local link [#1] .
97 | h5 wikiword link [wikiword] .
98 | . Paragraph.
99 | h6 .
100 | h6 link[] .
101 | h5 local link [#1] .
102 | h6 wikiword link [wikiword] .
103 | . Paragraph.
104 | . | dot at end is always header .
105 | . dot at end is always header $ .
106 | not a link [] .
107 | paragraph$ .
108 | wrong color of following square braces [] .
109 | wrong header color $ .
110 | [] .
111 | . Above is h1.
112 | [] .
113 | . Above is h2.
114 | | . text .
115 | . Above is h3.
116 | . [todo#] anchor.
117 |
--------------------------------------------------------------------------------
/test/xi_parser.mjs:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs/promises';
2 | import * as chai from 'chai';
3 | const expect = chai.expect;
4 |
5 | import * as parser from './../src/xi_parser.mjs';
6 |
7 |
8 | describe("Xi grammar parsing", () => {
9 |
10 | let _sample = null;
11 | const _cancel = {
12 | isCancellationRequested: false,
13 | };
14 |
15 |
16 | before("load sample", async () => {
17 | _sample = await fs.readFile('./test/test.xi', 'utf-8');
18 | });
19 |
20 |
21 | it("starts parsing", async () => {
22 | parser.parse(_sample).exist;
23 | });
24 |
25 |
26 | it("tokenisez input", async () => {
27 | parser.tokenize(_sample, _cancel).exist;
28 | });
29 |
30 |
31 | it("finds begin mark token", async () => {
32 | const tokens = parser.tokenize(" . foo", _cancel);
33 | expect(tokens).length(2);
34 | expect(tokens[0]).contains({type: parser.Token.BEGIN_MARK, pos: 0});
35 | expect(tokens[1]).contains({type: parser.Token.TEXT, pos: 4});
36 | });
37 |
38 |
39 | it("finds end mark token", async () => {
40 | const tokens = parser.tokenize("foo .", _cancel);
41 | expect(tokens).length(2);
42 | expect(tokens[0]).contains({type: parser.Token.TEXT, pos: 0});
43 | expect(tokens[1]).contains({type: parser.Token.END_MARK, pos: 3});
44 | });
45 |
46 |
47 | it("has indent info for end mark", async () => {
48 | // WIP
49 | return
50 | tokens = parser.tokenize(" foo .", _cancel);
51 | expect(tokens).length(3);
52 | expect(tokens[0]).contains({type: parser.Token.INDENT, pos: 0});
53 | expect(tokens[0]).contains({type: parser.Token.TEXT, pos: 2});
54 | expect(tokens[1]).contains({type: parser.Token.END_MARK, pos: 5});
55 | });
56 |
57 |
58 | it("has indent info for begin mark", async () => {
59 | // WIP
60 | return
61 | tokens = parser.tokenize(" . foo", _cancel);
62 | expect(tokens).length(3);
63 | expect(tokens[0]).contains({type: parser.Token.INDENT, pos: 0});
64 | expect(tokens[0]).contains({type: parser.Token.BEGIN_MARK, pos: 2});
65 | expect(tokens[0]).contains({type: parser.Token.TEXT, pos: 4});
66 | });
67 | });
68 |
--------------------------------------------------------------------------------