pair-mode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Owen Carpenter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # pair mode
2
+
3
+ A coding agent proposes an edit. Pair mode opens a side-by-side diff in a terminal
4
+ editor. Every line you type becomes a question the model must answer, and the edit
5
+ does not apply until you close the editor.
6
+
7
+ ## Install
8
+
9
+ ```
10
+ npm install -g pair-mode
11
+ pair-mode setup
12
+ ```
13
+
14
+ Install pair-mode globally before you run setup. Setup writes the install path into each
15
+ CLI's config as an absolute path. `npx pair-mode setup` runs from a package cache that
16
+ npm later prunes, which would leave every hook pointing at a deleted file. Setup detects
17
+ that case and stops.
18
+
19
+ The setup command detects the CLIs and multiplexers on your machine, registers the
20
+ required hooks, and writes a config file. Restart Claude Code after setup, because
21
+ Claude Code loads hooks only at startup.
22
+
23
+ If you install from git instead of npm, run `pnpm build` first. The package does not
24
+ ship a committed `dist/` directory; npm builds it fresh through the `prepublishOnly`
25
+ script, but a git checkout does not run that script.
26
+
27
+ ## Supported CLIs
28
+
29
+ | CLI | Hook | Status |
30
+ | ----------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------- |
31
+ | Claude Code | `PreToolUse`, matcher `Write\|Edit\|MultiEdit` | Shipped. |
32
+ | Codex | `PreToolUse`, matcher `apply_patch\|Edit\|Write` | Shipped. |
33
+ | pi | `tool_call` extension hook | Planned for 1.0.0. The adapter exists and passes its unit tests. Setup does not offer it yet. |
34
+ | opencode | `tool.execute.before` plugin hook | Planned for 1.0.0. Nobody has run it against a live opencode. Setup does not offer it yet. |
35
+
36
+ Codex has no `MultiEdit` matcher alias. Its `apply_patch` parser reads single-file Add,
37
+ Update, and Delete patches only. A multi-file or rename patch passes through untouched.
38
+
39
+ ## The /pair command
40
+
41
+ `pair-mode setup` installs a `/pair` command for every CLI whose hook it registers. The
42
+ command toggles pair mode for the current directory. It also tells the agent how a held
43
+ edit comes back.
44
+
45
+ | CLI | Installed at | Invoked as |
46
+ | ----------- | ------------------------------- | ----------------------- |
47
+ | Claude Code | `~/.claude/commands/pair.md` | `/pair on`, `/pair off` |
48
+ | Codex | `~/.codex/skills/pair/SKILL.md` | `$pair on`, `$pair off` |
49
+
50
+ Codex deprecated `~/.codex/prompts/` in favour of skills, so pair mode installs a skill
51
+ there. Codex invokes a skill with `$`, not `/`.
52
+
53
+ The command runs a bare `pair-mode`, unlike a hook, which each CLI invokes by absolute
54
+ path. So `pair-mode` must resolve on your PATH. A global install puts it there.
55
+
56
+ `pair-mode doctor` warns when the command is missing, and warns when `pair-mode` does not
57
+ resolve on PATH. Neither warning raises the exit code.
58
+
59
+ Setup backs up an existing file before it rewrites the file.
60
+
61
+ ## Editors
62
+
63
+ | Editor | Diff colour | Syntax colour on changed rows |
64
+ | -------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
65
+ | pair (default) | Yes | Yes. pair is the built-in review pane, so it paints syntax colour and change colour on the same row. It also reads mouse clicks and drags for selection, and it anchors a note to the exact span you select, not the whole line. |
66
+ | micro | Yes | No. micro paints one highlight group per character, so a changed row trades syntax colour for the diff band. |
67
+ | vim | Yes | Yes. `matchadd()` overlays the diff highlight on top of syntax, so a changed row keeps both. |
68
+ | nvim | Yes | Yes. Same `matchadd()` overlay as vim. |
69
+ | nano | Yes | No. nano has no syntax engine here, so a changed row gets the diff band only. |
70
+
71
+ Keys:
72
+
73
+ - pair: see [Keymap](#keymap) below.
74
+ - micro: Ctrl+W or F2 sends your annotations and closes the editor. F3 moves between
75
+ panes.
76
+ - vim and nvim: `:wqa` sends your annotations and closes the editor.
77
+
78
+ ## Keymap
79
+
80
+ These keys work inside the pair review pane. Some have a mouse equivalent; the rest are
81
+ keyboard only.
82
+
83
+ | Key | Mouse equivalent | Action |
84
+ | ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------ |
85
+ | `j` `k` `↓` `↑` | — | Move the row cursor. A fold moves as one row. |
86
+ | `Ctrl+d` `Ctrl+u` | — | Page down and page up. |
87
+ | `n` `N` | — | Jump to the next and the previous changed run. |
88
+ | `v`, then a motion | click and drag | Select a span. |
89
+ | `a` | — | Open a note on the current selection, or on the current row if nothing is selected. |
90
+ | `Enter` | — | Save the note. |
91
+ | `Esc` | — | Discard the note draft, or clear the current selection. |
92
+ | `Tab` | — | Cycle the focused note. |
93
+ | `d` | — | Delete the focused note. |
94
+ | `Space` | click a fold | Expand or collapse a fold row. |
95
+ | `u` | — | Swap between the split layout and the unified layout. |
96
+ | `Ctrl+s` | — | Send the notes. The hook denies the edit. |
97
+ | `Ctrl+q` `Ctrl+c` | — | Quit. With no notes, the edit applies. With notes pending, asks: `s` sends, `d` discards and quits, `Esc` cancels. |
98
+ | `?` | — | Toggle the keymap overlay. |
99
+
100
+ zellij consumes the mouse scroll wheel, so pair mode never depends on it. `Ctrl+d` and
101
+ `Ctrl+u` page instead.
102
+
103
+ Holding `shift` during a click or drag passes the mouse event straight through to the
104
+ terminal, which then selects text for copy — the same as any other terminal program.
105
+
106
+ ## Multiplexers
107
+
108
+ Claude Code and Codex run their hooks with no controlling terminal. `/dev/tty` returns
109
+ `ENXIO` there. Pane mode needs zellij or tmux to open an editor pane under those two
110
+ CLIs.
111
+
112
+ Session mode removes the requirement. See the next section.
113
+
114
+ ## Session modes
115
+
116
+ Pane mode is the default. The hook opens an editor in a floating pane and blocks. That
117
+ needs a multiplexer under Claude Code and Codex.
118
+
119
+ Session mode moves the review out of the agent's process. The hook posts to a Unix
120
+ socket and waits. A client you start renders the review and answers. There are two
121
+ clients, and both remove the multiplexer requirement.
122
+
123
+ Set `transport` to `"session"`, then pick a client.
124
+
125
+ ```
126
+ pair-mode config transport session
127
+ ```
128
+
129
+ `pair-mode config` with no argument prints every setting and its value. `pair-mode
130
+ config <key>` prints one. `pair-mode config <key> <value>` changes one and validates it
131
+ before it writes.
132
+
133
+ **Watch mode** reviews in a terminal you own.
134
+
135
+ ```
136
+ pair-mode watch # in any terminal, at the repo root
137
+ ```
138
+
139
+ The watcher paints an idle screen between reviews and opens the pair TUI for each one.
140
+ `q` on the idle screen quits and releases the socket.
141
+
142
+ **Web mode** reviews in a browser and needs no terminal at all.
143
+
144
+ ```
145
+ pair-mode on --web # prints a link
146
+ ```
147
+
148
+ That spawns a detached watcher, binds an HTTP server on `127.0.0.1`, and prints a link
149
+ carrying a random token. Drag across any text in the diff. A popup opens under the
150
+ selection. Type the note and press Enter. `pair-mode off` stops the watcher.
151
+
152
+ The watcher process binds the socket, so there is no separate daemon to manage. If the
153
+ watcher dies, the socket goes away and every hook fails open at once.
154
+
155
+ A hook that finds no watcher applies the edit rather than hanging. So does a hook whose
156
+ watcher never answers within `session.timeout`.
157
+
158
+ ## Configuration
159
+
160
+ Pair mode reads `$XDG_CONFIG_HOME/pair-mode/config.json`, or `~/.config/pair-mode/config.json`
161
+ when `XDG_CONFIG_HOME` is not set.
162
+
163
+ | Key | Type | Default | Meaning |
164
+ | ----------------- | --------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------- |
165
+ | `editor` | `"auto" \| "pair" \| "micro" \| "nvim" \| "vim" \| "nano"`, or an array of editor names | `"auto"` | Which editor opens the review pane. |
166
+ | `multiplexer` | `"auto" \| "zellij" \| "tmux" \| "none"` | `"auto"` | Which multiplexer hosts the pane. |
167
+ | `transport` | `"pane" \| "session"` | `"pane"` | Open a pane per edit, or post to a watcher you started. |
168
+ | `session.timeout` | integer, 1 or more | `300` | Seconds a hook waits for a verdict before it applies the edit. |
169
+ | `web.enabled` | boolean | `false` | Serve the review in a browser whenever a watcher starts. |
170
+ | `web.port` | integer, 0 to 65535 | `0` | `0` asks the operating system for a free port. |
171
+ | `layout` | `"split" \| "inline"` | `"split"` | Side-by-side columns, or one column. |
172
+ | `notes` | `"panel" \| "anchored"` | `"panel"` | A docked notes panel, or a note inline at its anchor. |
173
+ | `context` | integer, 1 or more | `5` | Unchanged lines kept around a change before folding. |
174
+ | `minFold` | integer, 1 or more | `4` | Minimum run of unchanged lines that folds. |
175
+ | `pane.width` | string | `"95%"` | |
176
+ | `pane.height` | string | `"95%"` | |
177
+ | `theme.add` | 6-digit hex colour | `"#1e3a1e"` | |
178
+ | `theme.del` | 6-digit hex colour | `"#3a1e1e"` | |
179
+ | `theme.fold` | 6-digit hex colour | `"#2a2a2a"` | |
180
+ | `theme.rowBand` | boolean | `true` | Paint the whole changed row, not just the changed span, in pair. |
181
+ | `syntax` | boolean | `true` | Load Shiki for syntax colour in pair. |
182
+ | `trace` | boolean | `false` | |
183
+ | `autoApprove` | boolean | `true` | |
184
+
185
+ An `editor` array lists editor names in order of preference. Pair mode tries each in
186
+ turn and uses the first one it finds on the machine. `auto` tries `pair` first, so an
187
+ existing config with no `editor` key now opens pair mode's own pane by default.
188
+
189
+ `notes` and `layout` both describe position, and they are easy to confuse. `layout`
190
+ controls the diff itself — split into two columns, or one inline column. `notes`
191
+ controls only where a note you write renders — in a docked panel, or anchored next to
192
+ the span it annotates. Setting one does not affect the other.
193
+
194
+ ## Limits
195
+
196
+ - Pair mode opens one pane per tool call. It cannot batch a changeset, because a hook
197
+ returns one verdict per call and an allow cannot be withdrawn.
198
+ - The pane reviews. It does not edit the proposal.
199
+ - Syntax colour in pair needs `shiki` installed. A missing package disables colour and
200
+ the pane still works.
201
+ - A client answers one review at a time. It cannot skip ahead in the queue.
202
+ - Web mode does not defend against another user on the same machine. The token sits in
203
+ the URL, and a local user can read the process list.
204
+ - Watch mode needs the watcher running before the agent edits. A missing watcher fails
205
+ open, so an unattended agent applies its edits.
206
+
207
+ ## License
208
+
209
+ MIT. See `LICENSE`.
@@ -0,0 +1,59 @@
1
+ filetype: c
2
+
3
+ detect:
4
+ filename: "(\\.(c|C)$|\\.(h|H)$|\\.ii?$|\\.(def)$)"
5
+
6
+ rules:
7
+ - identifier: "\\b[A-Z_][0-9A-Z_]+\\b"
8
+ - type: "\\b(_Atomic|_BitInt|float|double|_Decimal32|_Decimal64|_Decimal128|_Complex|complex|_Imaginary|imaginary|_Bool|bool|char|int|short|long|enum|void|struct|union|typedef|typeof|typeof_unqual|(un)?signed|inline|_Noreturn)\\b"
9
+ - type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr))|char(8|16|32)|wchar)_t\\b"
10
+ # GCC float/decimal/fixed types
11
+ - type: "\\b(_Float16|__fp16|_Float32|_Float32x|_Float64|_Float64x|__float80|_Float128|_Float128x|__float128|__ibm128|__int128|_Fract|_Sat|_Accum)\\b"
12
+ - type: "\\b[a-z_][0-9a-z_]+(_t|_T)\\b"
13
+ - statement: "\\b(auto|volatile|register|restrict|_Alignas|alignas|_Alignof|alignof|static|const|constexpr|extern|_Thread_local|thread_local)\\b"
14
+ - statement: "\\b(for|if|while|do|else|case|default|switch|_Generic|_Static_assert|static_assert)\\b"
15
+ - statement: "\\b(goto|continue|break|return)\\b"
16
+ - statement: "\\b(asm|fortran)\\b"
17
+ - preproc: "^[[:space:]]*#[[:space:]]*(define|embed|pragma|include|(un|ifn?)def|endif|el(if|ifdef|ifndef|se)|if|line|warning|error|__has_include|__has_embed|__has_c_attribute)"
18
+ - preproc: "^[[:space:]]*_Pragma\\b"
19
+ # GCC builtins
20
+ - statement: "__attribute__[[:space:]]*\\(\\([^)]*\\)\\)"
21
+ - statement: "__(aligned|asm|builtin|extension|hidden|inline|packed|restrict|section|typeof|weak)__"
22
+ # Operator Color
23
+ - symbol.operator: "[-+*/%=<>.:;,~&|^!?]|\\b(offsetof|sizeof)\\b"
24
+ - symbol.brackets: "[(){}]|\\[|\\]"
25
+ # Integer Constants
26
+ - constant.number: "(\\b([1-9][0-9]*|0[0-7]*|0[Xx][0-9A-Fa-f]+|0[Bb][01]+)([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\b)"
27
+ # Decimal Floating Constants
28
+ - constant.number: "(\\b(([0-9]*[.][0-9]+|[0-9]+[.][0-9]*)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+)[FfLl]?\\b)"
29
+ # Hexadecimal Floating Constants
30
+ - constant.number: "(\\b0[Xx]([0-9A-Za-z]*[.][0-9A-Za-z]+|[0-9A-Za-z]+[.][0-9A-Za-z]*)[Pp][+-]?[0-9]+[FfLl]?\\b)"
31
+ - constant.bool: "(\\b(true|false|NULL|nullptr|TRUE|FALSE)\\b)"
32
+
33
+ - constant.string:
34
+ start: '"'
35
+ end: '"'
36
+ skip: "\\\\."
37
+ rules:
38
+ - constant.specialChar: "\\\\([\"'abfnrtv\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})"
39
+
40
+ - constant.string:
41
+ start: "'"
42
+ end: "'"
43
+ skip: "\\\\."
44
+ rules:
45
+ # TODO: Revert back to - error: "..+" once #3127 is merged
46
+ - error: "[[:graph:]]{2,}'"
47
+ - constant.specialChar: "\\\\([\"'abfnrtv\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})"
48
+
49
+ - comment:
50
+ start: "//"
51
+ end: "$"
52
+ rules:
53
+ - todo: "(TODO|XXX|FIXME):?"
54
+
55
+ - comment:
56
+ start: "/\\*"
57
+ end: "\\*/"
58
+ rules:
59
+ - todo: "(TODO|XXX|FIXME):?"
@@ -0,0 +1,43 @@
1
+ filetype: css
2
+
3
+ detect:
4
+ filename: "\\.(css|scss)$"
5
+
6
+ rules:
7
+ # Classes and IDs
8
+ - statement: "(?i)."
9
+ # - normal:
10
+ # start: "\\{"
11
+ # end: "\\}"
12
+ # rules: []
13
+ # css commands
14
+ - type: "(align-content|align-items|alignment-baseline|align-self|all|animation|animation-delay|animation-direction|animation-duration|animation-fill-mode|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|appearance|azimuth|backdrop-filter|backface-visibility|background|background-attachment|background-blend-mode|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|baseline-shift|bookmark-label|bookmark-level|bookmark-state|border|border-bottom|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-boundary|border-collapse|border-color|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|border-right-color|border-right-style|border-right-width|border-spacing|border-style|border-top|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-width|bottom|box-decoration-break|box-shadow|box-sizing|box-snap|box-suppress|break-after|break-before|break-inside|caption-side|caret|caret-animation|caret-color|caret-shape|chains|clear|clip|clip-path|clip-rule|color|color-interpolation-filters|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|columns|column-span|column-width|content|continue|counter-increment|counter-reset|counter-set|cue|cue-after|cue-before|cursor|direction|display|dominant-baseline|elevation|empty-cells|filter|flex|flex-basis|flex-direction|flex-flow|flex-grow|flex-shrink|flex-wrap|float|float-defer|float-offset|float-reference|flood-color|flood-opacity|flow|flow-from|flow-into|font|font-family|font-feature-settings|font-kerning|font-language-override|font-size|font-size-adjust|font-stretch|font-style|font-synthesis|font-variant|font-variant-alternates|font-variant-caps|font-variant-east-asian|font-variant-ligatures|font-variant-numeric|font-variant-position|font-weight|footnote-display|footnote-policy|gap|glyph-orientation-vertical|grid|grid-area|grid-auto-columns|grid-auto-flow|grid-auto-rows|grid-column|grid-column-end|grid-column-gap|grid-column-start|grid-gap|grid-row|grid-row-end|grid-row-gap|grid-row-start|grid-template|grid-template-areas|grid-template-columns|grid-template-rows|hanging-punctuation|height|hyphenate-character|hyphenate-limit-chars|hyphenate-limit-last|hyphenate-limit-lines|hyphenate-limit-zone|hyphens|image-orientation|image-rendering|image-resolution|initial-letter|initial-letter-align|initial-letter-wrap|isolation|justify-content|justify-items|justify-self|left|letter-spacing|lighting-color|line-break|line-grid|line-height|line-snap|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|marker|marker-end|marker-knockout-left|marker-knockout-right|marker-mid|marker-pattern|marker-segment|marker-side|marker-start|marquee-direction|marquee-loop|marquee-speed|marquee-style|mask|mask-border|mask-border-mode|mask-border-outset|mask-border-repeat|mask-border-slice|mask-border-source|mask-border-width|mask-clip|mask-composite|mask-image|mask-mode|mask-origin|mask-position|mask-repeat|mask-size|mask-type|max-height|max-lines|max-width|min-height|min-width|mix-blend-mode|motion|motion-offset|motion-path|motion-rotation|nav-down|nav-left|nav-right|nav-up|object-fit|object-position|offset-after|offset-before|offset-end|offset-start|opacity|order|orphans|outline|outline-color|outline-offset|outline-style|outline-width|overflow|overflow-style|overflow-wrap|overflow-x|overflow-y|padding|padding-bottom|padding-left|padding-right|padding-top|page|page-break-after|page-break-before|page-break-inside|pause|pause-after|pause-before|perspective|perspective-origin|pitch|pitch-range|play-during|pointer-events|polar-anchor|polar-angle|polar-distance|polar-origin|position|presentation-level|quotes|region-fragment|resize|rest|rest-after|rest-before|richness|right|rotation|rotation-point|ruby-align|ruby-merge|ruby-position|running|scrollbar-color|scroll-behavior|scroll-snap-align|scroll-snap-margin|scroll-snap-margin-block|scroll-snap-margin-block-end|scroll-snap-margin-block-start|scroll-snap-margin-bottom|scroll-snap-margin-inline|scroll-snap-margin-inline-end|scroll-snap-margin-inline-start|scroll-snap-margin-left|scroll-snap-margin-right|scroll-snap-margin-top|scroll-snap-padding|scroll-snap-padding-block|scroll-snap-padding-block-end|scroll-snap-padding-block-start|scroll-snap-padding-bottom|scroll-snap-padding-inline|scroll-snap-padding-inline-end|scroll-snap-padding-inline-start|scroll-snap-padding-left|scroll-snap-padding-right|scroll-snap-padding-top|scroll-snap-type|shape-image-threshold|shape-inside|shape-margin|shape-outside|size|speak|speak-as|speak-header|speak-numeral|speak-punctuation|speech-rate|stress|string-set|stroke|stroke-alignment|stroke-dashadjust|stroke-dasharray|stroke-dashcorner|stroke-dashoffset|stroke-linecap|stroke-linejoin|stroke-miterlimit|stroke-opacity|stroke-width|table-layout|tab-size|text-align|text-align-all|text-align-last|text-combine-upright|text-decoration|text-decoration-color|text-decoration-line|text-decoration-skip|text-decoration-style|text-emphasis|text-emphasis-color|text-emphasis-position|text-emphasis-style|text-indent|text-justify|text-orientation|text-overflow|text-shadow|text-space-collapse|text-space-trim|text-spacing|text-transform|text-underline-offset|text-underline-position|text-wrap|top|transform|transform-box|transform-origin|transform-style|transition|transition-delay|transition-duration|transition-property|transition-timing-function|unicode-bidi|user-select|vertical-align|visibility|voice-balance|voice-duration|voice-family|voice-pitch|voice-range|voice-rate|voice-stress|voice-volume|volume|white-space|widows|width|will-change|word-break|word-spacing|word-wrap|wrap-after|wrap-before|wrap-flow|wrap-inside|wrap-through|writing-mode|z-index):"
15
+ # - default:
16
+ # start: ":"
17
+ # end: "[;^\\{]"
18
+ # rules: []
19
+ - special: "!important"
20
+ - identifier: ":active|:focus|:hover|:link|:visited|:link|:after|:before|$"
21
+ - special: "(\\{|\\}|\\(|\\)|\\;|:|\\]|~|<|>|,)"
22
+ # SCSS Varaibles
23
+ - statement: "@import|@mixin|@extend"
24
+ # Strings
25
+ - constant.string:
26
+ start: '"'
27
+ end: '"'
28
+ skip: "\\\\."
29
+ rules:
30
+ - constant.specialChar: "\\\\."
31
+ - constant.string:
32
+ start: "'"
33
+ end: "'"
34
+ skip: "\\\\."
35
+ rules:
36
+ - constant.specialChar: "\\\\."
37
+ - special: '"|'''
38
+ # Comments & TODOs
39
+ - comment:
40
+ start: "\\/\\*"
41
+ end: "\\*\\/"
42
+ rules:
43
+ - todo: "(TODO|XXX|FIXME):?"
@@ -0,0 +1,35 @@
1
+ filetype: dockerfile
2
+
3
+ detect:
4
+ filename: "((Docker|Container)file[^/]*$|\\.(docker|container)file$)"
5
+
6
+ rules:
7
+ ## Keywords
8
+ - type.keyword: "(?i)^(FROM|MAINTAINER|RUN|CMD|LABEL|EXPOSE|ENV|ADD|COPY|ENTRYPOINT|VOLUME|USER|WORKDIR|ONBUILD|ARG|HEALTHCHECK|STOPSIGNAL|SHELL)[[:space:]]"
9
+
10
+ ## Brackets & parenthesis
11
+ - statement: "(\\(|\\)|\\[|\\])"
12
+
13
+ ## Double ampersand
14
+ - special: "&&"
15
+
16
+ ## Comments
17
+ - comment:
18
+ start: "#"
19
+ end: "$"
20
+ rules:
21
+ - todo: "(TODO|XXX|FIXME):?"
22
+
23
+ - constant.string:
24
+ start: '"'
25
+ end: '"'
26
+ skip: "\\\\."
27
+ rules:
28
+ - constant.specialChar: "\\\\."
29
+
30
+ - constant.string:
31
+ start: "'"
32
+ end: "'"
33
+ skip: "\\\\."
34
+ rules:
35
+ - constant.specialChar: "\\\\."
@@ -0,0 +1,30 @@
1
+ filetype: elixir
2
+
3
+ detect:
4
+ filename: "\\.ex$|\\.exs$"
5
+
6
+ rules:
7
+ - statement: "\\b(abs|trunc|rem|div|round|max|min|and|or|not|throw|raise|reraise|hd|tl|in|length|elem|put_elem|destructure|to_(string|charlist)|is_(atom|binary|bitstring|boolean|float|function|integer|list|map|nil|number|pid|port|reference|tuple)|(bit|byte|map|tuple)_size|binary_part|def(delegate|exception|guard|guardp|impl|macro|macrop|module|overridable|p|protocol|struct)?|sigil_[crswCRSWDNT]|if|else|unless|cond|binding|node|self|spawn|spawn_link|spawn_monitor|send|exit|struct|get_and_update_in|get_in|put_in|pop_in|update_in|apply|inspect|make_ref|use|do|end)\\b"
8
+ - statement: "\\b(alias|import|require|case|fn|receive|after|try|catch|rescue|super|quote|unquote|unquote_splicing|for|with)\\b"
9
+
10
+ - constant: "\\b\\[A-Z]+\\b"
11
+ - constant.number: "\\b[0-9]+\\b"
12
+
13
+ - constant.string: "`[^`]*`|%x\\{[^}]*\\}"
14
+ - constant.string: "\"([^\"]|(\\\\\"))*\"|%[QW]?\\{[^}]*\\}|%[QW]?\\([^)]*\\)|%[QW]?<[^>]*>|%[QW]?\\[[^]]*\\]|%[QW]?\\$[^$]*\\$|%[QW]?\\^[^^]*\\^|%[QW]?![^!]*!"
15
+ - constant.string: "'([^']|(\\\\'))*'|%[qw]\\{[^}]*\\}|%[qw]\\([^)]*\\)|%[qw]<[^>]*>|%[qw]\\[[^]]*\\]|%[qw]\\$[^$]*\\$|%[qw]\\^[^^]*\\^|%[qw]![^!]*!"
16
+
17
+ - symbol.brackets: "\\{|\\}|\\[|\\]|\\(|\\)"
18
+
19
+ - comment: "#[^{].*$|#$"
20
+ - comment.bright: "##[^{].*$|##$"
21
+
22
+ - type.keyword: "\\:[a-zA-Z][a-zA-Z0-9_]*"
23
+ - type.keyword: "\\b(describe|test)"
24
+ - statement: "\\b(expected|assert|assert_raise|assert_in_delta|assert_received|catch_error|catch_throw|flunk|refute|refute_in_delta|refute_received)\\b"
25
+ - symbol.tag: "^\\s*\\@[a-zA-Z][a-zA-Z0-9_]*\\b"
26
+
27
+ - identifier.macro: "\\b(__CALLER__|__DIR__|__ENV__|__MODULE__|__aliases__|__block__|defmacro)\\b"
28
+
29
+ - todo: "(XXX|TODO|FIXME|\\?\\?\\?)"
30
+ - preproc.shebang: "\\W*#!.+?( |$)"
@@ -0,0 +1,47 @@
1
+ filetype: fish
2
+
3
+ detect:
4
+ filename: "\\.fish$"
5
+ header: "^#!.*/(env +)?fish( |$)"
6
+
7
+ rules:
8
+ # Numbers
9
+ - constant: "\\b[0-9]+\\b"
10
+
11
+ # Conditionals and control flow
12
+ - statement: "\\b(and|begin|break|case|continue|else|end|for|function|if|in|not|or|return|select|shift|switch|while)\\b"
13
+ - special: "(\\{|\\}|\\(|\\)|\\;|\\]|\\[|`|\\\\|\\$|<|>|^|!|=|&|\\|)"
14
+
15
+ # Fish commands
16
+ - type: "\\b(bg|bind|block|breakpoint|builtin|cd|count|command|commandline|complete|dirh|dirs|echo|emit|eval|exec|exit|fg|fish|fish_config|fish_ident|fish_pager|fish_prompt|fish_right_prompt|fish_update_completions|fishd|funced|funcsave|functions|help|history|jobs|math|mimedb|nextd|open|popd|prevd|psub|pushd|pwd|random|read|set|set_color|source|status|string|trap|type|ulimit|umask|vared)\\b"
17
+
18
+ # Common linux commands
19
+ - type: "\\b((g|ig)?awk|bash|dash|find|\\w{0,4}grep|kill|killall|\\w{0,4}less|make|pkill|sed|sh|tar)\\b"
20
+
21
+ # Coreutils commands
22
+ - type: "\\b(base64|basename|cat|chcon|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|dd|df|dir|dircolors|dirname|du|env|expand|expr|factor|false|fmt|fold|head|hostid|id|install|join|link|ln|logname|ls|md5sum|mkdir|mkfifo|mknod|mktemp|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|pr|printenv|printf|ptx|pwd|readlink|realpath|rm|rmdir|runcon|seq|(sha1|sha224|sha256|sha384|sha512)sum|shred|shuf|sleep|sort|split|stat|stdbuf|stty|sum|sync|tac|tail|tee|test|time|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|users|vdir|wc|who|whoami|yes)\\b"
23
+
24
+ # Conditional flags
25
+ - statement: "--[a-z-]+"
26
+ - statement: "\\ -[a-z]+"
27
+
28
+ - identifier: "(?i)\\{?\\$[0-9A-Z_!@#$*?-]+\\}?"
29
+
30
+ - constant.string:
31
+ start: '"'
32
+ end: '"'
33
+ skip: "\\\\."
34
+ rules:
35
+ - constant.specialChar: "\\\\."
36
+
37
+ - constant.string:
38
+ start: "'"
39
+ end: "'"
40
+ skip: "\\\\."
41
+ rules: []
42
+
43
+ - comment:
44
+ start: "#"
45
+ end: "$"
46
+ rules:
47
+ - todo: "(TODO|XXX|FIXME):?"
@@ -0,0 +1,62 @@
1
+ filetype: go
2
+
3
+ detect:
4
+ filename: "\\.go$"
5
+
6
+ rules:
7
+ # Conditionals and control flow
8
+ - special: "\\b(break|case|continue|default|go|goto|range|return|println|fallthrough)\\b"
9
+ - statement: "\\b(else|for|if|switch|select)\\b"
10
+ - preproc: "\\b(package|import|const|var|type|struct|func|defer|iota|make|new|copy|len|cap|panic|append|close|delete|print|recover)\\b"
11
+ - symbol.operator: "[-+/*=<>!~%&|^]|:="
12
+
13
+ # Types
14
+ - symbol: "(,|\\.)"
15
+ - type: "\\b(u?int(8|16|32|64)?|float(32|64)|complex(64|128))\\b"
16
+ - type: "\\b(uintptr|byte|rune|string|interface|bool|map|chan|error)\\b"
17
+ - type.keyword: "\\b(struct)\\b"
18
+ - constant.bool: "\\b(true|false|nil)\\b"
19
+
20
+ # Brackets
21
+ - symbol.brackets: "(\\{|\\})"
22
+ - symbol.brackets: "(\\(|\\))"
23
+ - symbol.brackets: "(\\[|\\])"
24
+
25
+ # Numbers and strings
26
+ - constant.number: "\\b([0-9]+|0x[0-9a-fA-F]*)\\b|'.'"
27
+
28
+ - constant.string:
29
+ start: '"'
30
+ end: '"'
31
+ skip: "\\\\."
32
+ rules:
33
+ - constant.specialChar: "%."
34
+ - constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
35
+ - constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
36
+
37
+ - constant.string:
38
+ start: "'"
39
+ end: "'"
40
+ skip: "\\\\."
41
+ rules:
42
+ - error: "..+"
43
+ - constant.specialChar: "%."
44
+ - constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
45
+ - constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
46
+
47
+ - constant.string:
48
+ start: "`"
49
+ end: "`"
50
+ rules: []
51
+
52
+ - comment:
53
+ start: "//"
54
+ end: "$"
55
+ rules:
56
+ - todo: "(TODO|XXX|FIXME):?"
57
+
58
+ - comment:
59
+ start: "/\\*"
60
+ end: "\\*/"
61
+ rules:
62
+ - todo: "(TODO|XXX|FIXME):?"
@@ -0,0 +1,70 @@
1
+ filetype: html
2
+
3
+ detect:
4
+ filename: "\\.htm[l]?$"
5
+
6
+ rules:
7
+ # Doctype is case-insensitive
8
+ - preproc: "<!(?i)(DOCTYPE html.*)>"
9
+ # Opening tag
10
+ - symbol.tag:
11
+ start: "<(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dialog|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h[1-6]|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|main|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|section|select|small|source|span|strike|strong|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr)\\b"
12
+ end: ">"
13
+ rules:
14
+ - identifier: "\\b(placeholder|style|alt|bgcolor|height|href|id|(aria|data)\\-.+|label|longdesc|name|on(click|focus|load|mouseover)|size|span|src|target|type|value|width|class|charset|content|rel|integrity|crossorigin|for|onsubmit|lang|role)\\b"
15
+ - special: "\\b(required)\\b"
16
+ # Match double-quote strings
17
+ - constant.string:
18
+ start: '"'
19
+ end: '"'
20
+ skip: "\\\\."
21
+ rules:
22
+ - constant.specialChar: "\\\\."
23
+ - constant.string.url: "((ftp(s)?|http(s)?|git|chrome)://[^\\s]+)"
24
+ # Match single-quote strings
25
+ - constant.string:
26
+ start: "'"
27
+ end: "'"
28
+ skip: "\\\\."
29
+ rules:
30
+ - constant.specialChar: "\\\\."
31
+ - constant.string.url: "((ftp(s)?|http(s)?|git|chrome)://[^\\s]+)"
32
+ # Highlight the equals and any colon between words
33
+ - symbol: "\\b(=|:\\b)"
34
+
35
+ # Closing tag
36
+ - symbol.tag:
37
+ start: "</(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dialog|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h[1-6]|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|main|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|section|select|small|source|span|strike|strong|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr)\\b"
38
+ end: ">"
39
+ rules:
40
+ # Anything in the closing tag is an error
41
+ - error: "."
42
+
43
+ # Reserved entities like a&#12; &nbsp; and &#x12A;
44
+ - special: "(([a-zA-Z]&#[0-9]+|&[a-zA-Z]+|&#[a-zA-Z0-9]+);)"
45
+
46
+ # TODO: Add `limit-rules` to both the `default` rules below once it's implemented into Micro
47
+ - default:
48
+ start: "<script.*?>"
49
+ end: "</script.*?>"
50
+ limit-group: symbol.tag
51
+ rules:
52
+ - include: "javascript"
53
+
54
+ - default:
55
+ start: "<style.*?>"
56
+ end: "</style.*?>"
57
+ limit-group: symbol.tag
58
+ rules:
59
+ - include: "css"
60
+
61
+ # This weird empty comment thing is technically valid
62
+ - comment: "<!>"
63
+
64
+ - comment.block:
65
+ start: "<!\\-\\-"
66
+ end: "\\-\\->"
67
+ rules:
68
+ - todo: "(FIXME|NOTE|TODO):?"
69
+ # While technically not a "true" error, these are recommended to not be used inside a comment
70
+ - error: "(\\-\\-|>)"