├── .gitignore
├── .travis.yml
├── .vscode
├── launch.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── License.txt
├── README.md
├── buildCodiconNames.js
├── package-lock.json
├── package.json
├── package.nls.json
├── package.nls.zh-cn.json
├── resources
├── button-icons
│ ├── clear-filter.png
│ ├── collapse.png
│ ├── expand.png
│ ├── export.png
│ ├── filter.png
│ ├── flat.png
│ ├── notag.png
│ ├── refresh.png
│ ├── reveal.png
│ ├── scan-current-file.png
│ ├── scan-open-files.png
│ ├── scan-workspace-only.png
│ ├── scan-workspace.png
│ ├── tag.png
│ ├── tags.png
│ └── tree.png
├── icons
│ ├── dark
│ │ └── todo-green.svg
│ ├── license.txt
│ └── light
│ │ ├── clear-filter.png
│ │ ├── collapse.png
│ │ ├── expand.png
│ │ ├── filter.png
│ │ ├── flat.png
│ │ ├── notag.png
│ │ ├── refresh.png
│ │ ├── refresh.svg
│ │ ├── reveal.png
│ │ ├── scan-open-files.png
│ │ ├── scan-workspace.png
│ │ ├── tag.png
│ │ ├── tags.png
│ │ ├── todo-green.svg
│ │ └── tree.png
├── screenshot.png
├── todo-tree-container.svg
└── todo-tree.png
├── src
├── attributes.js
├── codiconNames.js
├── colourNames.js
├── colours.js
├── config.js
├── extension.js
├── highlights.js
├── icons.js
├── productIconNames.js
├── ripgrep.js
├── searchResults.js
├── themeColourNames.js
├── tree.js
└── utils.js
├── test
├── stubs.js
└── tests.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | test-files
2 | node_modules
3 | dist
4 |
5 | # https://github.com/github/gitignore/blob/master/Global/VisualStudioCode.gitignore
6 | .vscode/*
7 | !.vscode/tasks.json
8 | !.vscode/launch.json
9 |
10 | *.vsix
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | os:
4 | - osx
5 | - linux
6 |
7 | dist: bionic
8 |
9 | before_install:
10 | - npm install
11 |
12 | script:
13 | - npm test
14 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [{
8 | "name": "Run Extension",
9 | "type": "extensionHost",
10 | "request": "launch",
11 | "runtimeExecutable": "${execPath}",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ]
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "ut",
8 | "type": "shell",
9 | "command": "qunit",
10 | "presentation": {
11 | "reveal": "always"
12 | },
13 | "problemMatcher": {
14 | "owner": "ut",
15 | "fileLocation": [
16 | "absolute"
17 | ],
18 | "pattern": {
19 | "regexp": "^.*\\((.*):(\\d+):(\\d+)\\)$",
20 | "file": 1,
21 | "line": 2,
22 | "column": 3
23 | }
24 | },
25 | "group": {
26 | "kind": "test",
27 | "isDefault": true
28 | }
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .gitignore
3 | node_modules/
4 | src/
5 | test/
6 | test-files
7 | webpack.config.js
8 | dist/extension.js.map
9 |
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 |
2 | Todo Tree
3 |
4 | Copyright (c) Nigel Scott
5 |
6 | All rights reserved.
7 |
8 | MIT License
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Todo Tree
2 |
3 | This extension quickly searches (using [ripgrep](https://github.com/BurntSushi/ripgrep)) your workspace for comment tags like TODO and FIXME, and displays them in a tree view in the activity bar. The view can be dragged out of the activity bar into the explorer pane (or anywhere else you would prefer it to be).
4 |
5 | Clicking a TODO within the tree will open the file and put the cursor on the line containing the TODO.
6 |
7 | Found TODOs can also be highlighted in open files.
8 |
9 | *Please see the [wiki](https://github.com/Gruntfuggly/todo-tree/wiki/Configuration-Examples) for configuration examples.*
10 |
11 | 
12 |
13 | *Notes:*
14 |
15 | - *User* `rg.conf` *files are ignored.*
16 |
17 | ## Highlighting
18 |
19 | >*New!:* If you just want to set different colours for tags, you can now enable `todo-tree.highlights.useColourScheme`. This will apply a set of colours (which can be changed) to the tags in the order that they are defined.
20 |
21 | Highlighting tags is configurable. Use `defaultHighlight` to set up highlights for all tags. If you need to configure individual tags differently, use `customHighlight`. If settings are not specified in `customHighlight`, the value from `defaultHighlight` is used.
22 |
23 | Custom highlights can also be specified for sub tags (if used).
24 |
25 | *Note: `defaultHighlight` is not applied to sub tags.*
26 |
27 | Both `defaultHighlight` and `customHighlight` allow for the following settings:
28 |
29 | `foreground` - used to set the foreground colour of the highlight in the editor and the marker in the ruler.
30 |
31 | `background` - used to set the background colour of the highlight in the editor.
32 |
33 | *Note: Foreground and background colours can be specified using [HTML/CSS colour names](https://en.wikipedia.org/wiki/Web_colors) (e.g. "Salmon"), RGB hex values (e.g. "#80FF00"), RGB CSS style values (e.g. "rgb(255,128,0)" or colours from the current theme, e.g. `peekViewResult.background`. See [Theme Color](https://code.visualstudio.com/api/references/theme-color) for the details. Hex and RGB values can also have an alpha specified, e.g. "#ff800080" or "rgba(255,128,0,0.5)".*
34 |
35 | `opacity` - percentage value used with the background colour. 100% will produce an opaque background which will obscure selection and other decorations.
36 |
37 | *Note: opacity can only be specified when hex or RGB colours are used.*
38 |
39 | `fontWeight`, `fontStyle`, `textDecoration` - can be used to style the highlight with standard CSS values.
40 |
41 | `borderRadius` - used to set the border radius of the background of the highlight.
42 |
43 | `icon` - used to set a different icon in the tree view. Must be a valid octicon (see ) or codicon (see ). If using codicons, specify them in the format "$(*icon*)". The icon defaults to a tick if it's not valid. You can also use "todo-tree", or "todo-tree-filled" if you want to use the icon from the activity view.
44 |
45 | `iconColour` - used to set the colour of the icon in the tree. If not specified, it will try to use the foreground colour or the background colour. Colour can be specified as per foreground and background colours, but see note below.
46 |
47 | *Note: Theme colours are only supported when using codicons. Hex, RGB and HTML colours are only supported when using octicons or the default icon.*
48 |
49 | `gutterIcon` - set to true to show the icon in the editor gutter.
50 |
51 | *Note: Unfortunately, only octicons and the todo-tree icon can be displayed in the gutter.*
52 |
53 | `rulerColour` - used to set the colour of the marker in the overview ruler. If not specified, it will default to use the foreground colour. Colour can be specified as per foreground and background colours.
54 |
55 | `rulerOpacity` - used to set the opacity of the ruler markers.
56 |
57 | *Note: Only works with hex and RGB colour settings.*
58 |
59 | `rulerLane` - used to set the lane for the marker in the overview ruler. If not specified, it will default to the right hand lane. Use one of "left", "center", "right", or "full". You can also use "none" to disable the ruler markers.
60 |
61 | `type` - used to control how much is highlighted in the editor. Valid values are:
62 |
63 | - `tag` - highlights just the tag
64 | - `text` - highlights the tag and any text after the tag
65 | - `tag-and-comment` - highlights the comment characters (or the start of the match) and the tag
66 | - `tag-and-subTag` - as above, but allows the sub tag to be highlight too (with colours defined in custom highlights)
67 | - `text-and-comment` - highlights the comment characters (or the start of the match), the tag and the text after the tag
68 | - `line` - highlights the entire line containing the tag
69 | - `whole-line` - highlights the entire line containing the tag to the full width of the editor
70 | - `capture-groups:n,m...` - highlights capture groups from the regex, where 'n' is the index into the regex
71 | - `none` - disable highlightling in the document
72 |
73 | `hideFromTree` - used to hide tags from the tree, but still highlight in files
74 |
75 | `hideFromStatusBar` - prevents the tag from being included in the status bar counts
76 |
77 | `hideFromActivityBar` - prevents the tag from being included in the activity bar badge count
78 |
79 | Example:
80 |
81 | ```json
82 | "todo-tree.highlights.defaultHighlight": {
83 | "icon": "alert",
84 | "type": "text",
85 | "foreground": "red",
86 | "background": "white",
87 | "opacity": 50,
88 | "iconColour": "blue"
89 | },
90 | "todo-tree.highlights.customHighlight": {
91 | "TODO": {
92 | "icon": "check",
93 | "type": "line"
94 | },
95 | "FIXME": {
96 | "foreground": "black",
97 | "iconColour": "yellow",
98 | "gutterIcon": true
99 | }
100 | }
101 | ```
102 |
103 | *Note: The highlight configuration is separate from the settings for the search. Adding settings in `customHighlight` does not automatically add the tags into `todo-tree.general.tags`.*
104 |
105 | *Note: Using the `capture-groups` setting in `type` may have an impact on performance with large files.
106 |
107 | ## Installing
108 |
109 | You can install the latest version of the extension via the Visual Studio Marketplace [here](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.todo-tree).
110 |
111 | Alternatively, open Visual Studio code, press `Ctrl+P` or `Cmd+P` and type:
112 |
113 | > ext install Gruntfuggly.todo-tree
114 |
115 | ### Source Code
116 |
117 | The source code is available on GitHub [here](https://github.com/Gruntfuggly/todo-tree).
118 |
119 | ## Controls
120 |
121 | The tree view header can contain the following buttons:
122 |
123 |  - Collapse all tree nodes
124 |  - Expand all tree nodes
125 |  - Show the tree view as a flat list, with the full filename for each TODO
126 |  - Show the view as a list of tags
127 |  - Show the tree view as a tree with expandable nodes for each folder (default)
128 |  - Group the TODOs in the tree by the tag
129 |  - Organise the TODOs by file (default)
130 |  - Only show items in the tree which match the entered filter text
131 |  - Remove any active filter
132 |  - Rebuild the tree
133 |  - Show tags from open files only
134 |  - Show tags from the current file
135 |  - Show tags from workspace only
136 |  - Show tags from workspace and open files
137 |  - Show the current file in the tree
138 |  - Export the tree content to a file
139 |
140 | ## Folder Filter Context Menu
141 |
142 | Right clicking in the tree view will bring up a context menu with the following options:
143 |
144 | **Hide This Folder/Hide This File** - removes the folder or file from the tree
145 | **Only Show This Folder** - remove all other folders and subfolders from the tree
146 | **Only Show This Folder And Subfolders** - remove other folders from the tree, but keep subfolders
147 | **Reset Folder Filter** - reset any folders previously filtered using the above
148 | **Toggle Badges** - enable/disable SCM decorations
149 | **Toggle Compact Folders** - enable/disable compressed folder names
150 | **Toggle Item Counts** - enable/disable counts of tags
151 | **Scan Open Files Only** - show TODOs from files open in VSCode (no search)
152 | **Scan Current File Only** - show TODOs from the current open file only
153 | **Scan Workspace And Open Files** - show TODOs from the workspace and any open files
154 | **Scan Workspace Only** - show TODOs from the workspace only (requires manual refresh)
155 | **Expand Tree/Collapse Tree** - expand or collapse the whole tree
156 | **Show Tree View/Show Flat View/Show Tags Only View** - change the tree view style
157 | **Group by Tag/Ungroup by Tag** - toggle grouping of items by tag
158 | **Group by Sub Tag/Ungroup by Sub Tag** - toggle grouping of items by sub tag
159 | **Export Tree** - dump the tree contents into a file
160 | **Reveal Current File in Tree** - show the current editor file in tree (if present)
161 |
162 | *Note: The current filters are shown in the debug log. Also, the filter can always be reset by right clicking the **Nothing Found** item in the tree. If your tree becomes invisible because everything is filtered and `hideTreeWhenEmpty` is set to true, you can reset the filter by pressing **F1** and selecting the **Todo Tree: Reset Folder Filter** command.*
163 |
164 | ## Commands
165 |
166 | ### Tags
167 |
168 | To make it easier to configure the tags, there are two commands available:
169 |
170 | **Todo Tree: Add Tag** - allows entry of a new tag for searching
171 |
172 | **Todo Tree: Remove Tag** - shows a list of current tags which can be selected for removing
173 |
174 | *Note: The Remove Tag command can be used to show current tags - just press Escape or Enter with out selecting any to close it.*
175 |
176 | ### Export
177 |
178 | The contents of the tree can be exported using **Todo Tree: Export Tree**. A read-only file will be created using the path specified with `todo-tree.general.exportPath`. The file can be saved using **File: Save As...**. *Note: Currently **File: Save** does not work which seems to be a VSCode bug (see ).*
179 |
180 | ### Switch Scope
181 |
182 | **Todo Tree: Switch Scope** - shows a list of configured scopes which can be selected
183 |
184 | ### Navigation
185 |
186 | The commands **Todo Tree: Go To Next** and **Todo Tree: Go To Previous** can be used to move to the next and previous TODO within the current editor.
187 |
188 | ## Configuration
189 |
190 | The extension can be customised as follows (default values in brackets):
191 |
192 | **todo-tree.general.debug** (`false`)
193 | Show a debug channel in the output view.
194 |
195 | **todo-tree.general.periodicRefreshInteval** (`0`)
196 | Interval (in minutes) for automatically refreshing the tree. Set to '0' to disable, or to the number of minutes between refreshes. *Note: This is not typically needed as the tree will be refreshed when files change.*
197 |
198 | **todo-tree.general.automaticGitRefreshInterval** (`0`)
199 | Polling interval (in seconds) for automatically refreshing the tree when your repository is updated. This will check if your repository HEAD has changed and trigger a rescan of the workspace if it has. This replaces the file watcher functionality. Set to '0' to disable, or to the number of seconds between checks.
200 |
201 | **todo-tree.general.exportPath** (`~/todo-tree-%Y%m%d-%H%M.txt`)
202 | Path to use when exporting the tree. Environment variables will be expanded, e.g `${HOME}` and the path is passed through strftime (see ). Set the extension to `.json` to export as a JSON record.
203 |
204 | **todo-tree.general.rootFolder** (`""`)
205 | By default, any open workspaces will have a tree in the view. Use this to force another folder to be the root of the tree. You can include environment variables and also use ${workspaceFolder}. e.g.
206 | `"todo-tree.general.rootFolder": "${workspaceFolder}/test"`
207 | or
208 | `"todo-tree.general.rootFolder": "${HOME}/project"`.
209 |
210 |
211 | *Note: Other open files (outside of the rootFolder) will be shown (as they are opened) with their full path in brackets.*
212 |
213 | **todo-tree.general.schemes** (`['file','ssh','untitled']`)
214 | Editor schemes to find TODOs in. To find TODOs in settings files, for instance, add `vscode-userdata` or for output windows, add `output`.
215 |
216 | **todo-tree.general.tags** (`["TODO","FIXME","BUG"]`)
217 | This defines the tags which are recognised as TODOs. This list is automatically inserted into the regex.
218 |
219 | **todo-tree.general.tagGroups** (`{}`)
220 | This setting allows multiple tags to be treated as a single group. Example:
221 |
222 | ```json
223 | "todo-tree.general.tagGroups": {
224 | "FIXME": [
225 | "FIXME",
226 | "FIXIT",
227 | "FIX",
228 | ]
229 | },
230 | ```
231 |
232 | This treats any of `FIXME`, `FIXIT` or `FIX` as `FIXME`. When the tree is grouped by tag, all of these will appear under the `FIXME` node. This also means that custom highlights are applied to the group, not each tag type.
233 |
234 | *Note: all tags in the group should also appear in `todo-tree.general.tags`.*
235 |
236 | **todo-tree.general.revealBehaviour** (`start of todo`)
237 | Change the cursor behaviour when double-clicking a todo in the tree. You can choose from: `start of todo` (moves the cursor to the beginning of the todo), `end of todo` (moves the cursor to the end of the todo) or `start of line` (moves the cursor to the start of the line).
238 |
239 | **todo-tree.general.statusBar** (`none`)
240 | What to show in the status bar - nothing (`none`), total count (`total`), counts per tag (`tags`), counts for the top three tags (`top three`) or counts for the current file only (`current file`).
241 |
242 | **todo-tree.general.statusBarClickBehaviour** (`reveal`)
243 | Set the behaviour of clicking the status bar to either cycle through the status bar display formats (`cycle`), reveal the tree (`reveal`) or to toggle highlights (`toggle highlights`).
244 |
245 | **todo-tree.general.showIconsInsteadOfTagsInStatusBar** (`false`)
246 | Show icons instead of tags in the status bar.
247 |
248 | **todo-tree.general.showActivityBarBadge** (`false`)
249 | Show a badge in the activity bar indicating the total number of found TODOs.
250 |
251 | *Note: When the tree view is in the Explorer pane, the badge is displayed on the Explorer icon, which may not be desirable.*
252 |
253 | **todo-tree.filtering.includeGlobs** (`[]`)
254 | Globs for use in limiting search results by inclusion, e.g. `[\"**/unit-tests/*.js\"]` to only show .js files in unit-tests subfolders. [Globs help](https://code.visualstudio.com/api/references/vscode-api#GlobPattern).
255 |
256 | *Note: globs paths are absolute - not relative to the current workspace.*
257 |
258 | **todo-tree.filtering.excludeGlobs** (`["**/node_modules/*/**"]`)
259 | Globs for use in limiting search results by exclusion (applied after **includeGlobs**), e.g. `[\"**/*.txt\"]` to ignore all .txt files.
260 |
261 | *Note: `node_modules` are excluded by default.*
262 |
263 | **todo-tree.filtering.includedWorkspaces** (`[]`)
264 | A list of workspace names to include as roots in the tree (wildcards can be used). An empty array includes all workspace folders.
265 |
266 | **todo-tree.filtering.excludedWorkspaces** (`[]`)
267 | A list of workspace names to exclude as roots in the tree (wildcards can be used).
268 |
269 | **todo-tree.filtering.passGlobsToRipgrep** (`true`)
270 | Set this to false to apply the globs *after* the search (legacy behaviour).
271 |
272 | **todo-tree.filtering.useBuiltInExcludes** (`none`)
273 | Set this to use VSCode's built in files or search excludes. Can be one of `none`, `file excludes` (uses Files:Exclude), `search excludes` (Uses Search:Exclude) or `file and search excludes` (uses both).
274 |
275 | **todo-tree.filtering.ignoreGitSubmodules** (`false`)
276 | If true, any subfolders containing a `.git` file will be ignored when searching.
277 |
278 | **todo-tree.filtering.includeHiddenFiles** (`false`)
279 | If true, files starting with a period (.) will be included.
280 |
281 | **todo-tree.highlights.enabled** (`true`)
282 | Set this to false to turn off highlighting.
283 |
284 | **todo-tree.highlights.highlightDelay** (`500`)
285 | The delay before highlighting (milliseconds).
286 |
287 | **todo-tree.highlights.defaultHighlight** (`{}`)
288 | Set default highlights. Example:
289 |
290 | ```json
291 | {
292 | "foreground": "white",
293 | "background": "red",
294 | "icon": "check",
295 | "type": "text"
296 | }
297 | ```
298 |
299 | **todo-tree.highlights.customHighlight** (`{}`)
300 | Set highlights per tag (or tag group). Example:
301 |
302 | ```json
303 | {
304 | "TODO": {
305 | "foreground": "white",
306 | "type": "text"
307 | },
308 | "FIXME": {
309 | "icon": "beaker"
310 | }
311 | }
312 | ```
313 |
314 | **todo-tree.highlights.useColourScheme** (`false`)
315 | Use a simple scheme for colouring highlights. This will simply apply a list of colours in the same order as the tags are defined. Use this as a much simpler alternative to setting up custom highlights for each tag.
316 |
317 | *Note: The colour scheme overrides the colours defined in* `todo-tree.highlights.defaultHighlight` *but not* `todo-tree.highlights.customHighlight`*.*
318 |
319 | **todo-tree.highlights.backgroundColourScheme** (`["red","orange","yellow","green","blue","indigo","violet"]`)
320 | Defines colours for use in conjunction with `todo-tree.highlights.useColourScheme` to colour highlights. Colours can be defined in the same way as other colours (e.g. hex code, theme names, etc.). If there are more tags than colours, the sequence is repeated.
321 |
322 | **todo-tree.highlights.foreroundColourScheme** (`["white","black","black","white","white","white","black"]`)
323 | Defines colours for use in conjunction with `todo-tree.highlights.backgroundColourScheme` to colour highlights. These colours should be complementary to the background colours.
324 |
325 | **todo-tree.regex.enableMultiLine** (`false`)
326 | Normally, multiline support is enabled by detecting the use of `\n` in the regex. Set this to `true`, to enable multiline support by default. This allows the use of `[\s\S]` as an alternative to matching any character including newlines.
327 |
328 | **todo-tree.regex.regex** (
329 | (//|#|<!--|;|/\\*|^|^[ \\t]*(-|\\d+.))\\s*($TAGS))
330 |
331 | This defines the regex used to locate TODOs. By default, it searches for tags in comments starting with //, #, ;, <!-- or /*, and also markdown todo lists. This should cover most languages. However if you want to refine it, make sure that the ($TAGS) is kept as ($TAGS) will be replaced by the expanded tag list. For some of the extension features to work, ($TAGS) should be present in the regex, however, the basic functionality should still work if you need to explicitly expand the tag list.
332 |
333 | *Note: This is a [Rust regular expression](https://docs.rs/regex/1.0.0/regex), not javascript.*
334 |
335 | **todo-tree.regex.subTagRegex**
336 | This is a regular expression for processing the text to the right of the tag, e.g. for extracting a sub tag, or removing unwanted characters. Anything that the regex matches will be removed from the text. If a capture group is included, the contents are extracted into a sub tag, which will be used in the tree to group similar tags. The sub tag can also be used as a placeholder in `todo-tree.tree.subTagClickUrl` and `todo-tree.tree.labelFormat`. Sub tags can also be highlighted by specifying a section in the `todo-tree.highlights.customHighlights` setting. To highlight the sub tag itself, set "type" to "tag-and-subTag" in custom highlights for the tag.
337 |
338 | Examples:
339 |
340 | `"^:\s*"` can be used to remove colons from immediately after tags.
341 |
342 | `"^\s*\((.*)\)"` can be used to extract anything in parentheses after the tag and use it as a sub tag.
343 |
344 | **todo-tree.regex.regexCaseSensitive** (`true`)
345 | Set to false to allow tags to be matched regardless of case.
346 |
347 | **todo-tree.ripgrep.ripgrep** (`""`)
348 | Normally, the extension will locate ripgrep itself as and when required. If you want to use an alternate version of ripgrep, set this to point to wherever it is installed.
349 |
350 | **todo-tree.ripgrep.ripgrepArgs** (`"--max-columns=1000"`)
351 | Use this to pass additional arguments to ripgrep. e.g. `"-i"` to make the search case insensitive. *Use with caution!*
352 |
353 | **todo-tree.ripgrep.ripgrepMaxBuffer** (`200`)
354 | By default, the ripgrep process will have a buffer of 200KB. However, this is sometimes not enough for all the tags you might want to see. This setting can be used to increase the buffer size accordingly.
355 |
356 | **todo-tree.ripgrep.usePatternFile** (`true`)
357 | A pattern file is used with ripgrep by default. If you experience issues with deleting the pattern file, set this to false to use the legacy method of providing the regex to ripgrep.
358 |
359 | **todo-tree.tree.hideTreeWhenEmpty** (`true`)
360 | Normally, the tree is removed from the explorer view if nothing is found. Set this to false to keep the view present.
361 |
362 | **todo-tree.tree.filterCaseSensitive** (`false`)
363 | Use this if you need the filtering to be case sensitive.
364 |
365 | *Note: this does not the apply to the search*.
366 |
367 | **todo-tree.tree.trackFile** (`true`)
368 | Set to false if you want to prevent tracking the open file in the tree view.
369 |
370 | **todo-tree.tree.showBadges** (`true`)
371 | Set to false to disable SCM status and badges in the tree. *
372 |
373 | *Note: This also unfortunately turns off themed icons.*
374 |
375 | **todo-tree.tree.expanded*** (`false`)
376 | Set to true if you want new views to be expanded by default.
377 |
378 | **todo-tree.tree.flat*** (`false`)
379 | Set to true if you want new views to be flat by default.
380 |
381 | **todo-tree.tree.grouped*** (`false`)
382 | Set to true if you want new views to be grouped by default.
383 |
384 | **todo-tree.tree.tagsOnly*** (`false`)
385 | Set to true if you want new views with tags only by default.
386 |
387 | **todo-tree.tree.sortTagsOnlyViewAlphabetically** (`false`)
388 | Sort items in the tags only view alphabetically instead of in order of the tags list.
389 |
390 | **todo-tree.tree.showCountsInTree** (`false`)
391 | Set to true to show counts of TODOs in the tree.
392 |
393 | **todo-tree.tree.labelFormat** (`${tag} ${after}`)
394 | Format of the TODO item labels. Available placeholders are `${line}`, `${column}`, `${tag}`, `${before}` (text from before the tag), `${after}` (text from after the tag), `${filename}`, `${filepath}` and `${afterOrBefore}` (use "after" text or "before" text if after is empty). When using `${tag}` or `${subTag}` you can also transform the text with "uppercase", "lowercase" or "capitalize", e.g. `${tag:lowercase}`.
395 |
396 | **todo-tree.tree.scanMode** (`workspace`)
397 | By default the extension scans the whole workspace (`workspace`). Use this to limit the search to only open files (`open files`) or only the current file (`current file`).
398 |
399 | **todo-tree.tree.showScanModeButton** (`false`)
400 | Show a button on the tree view header to switch the scanMode (see above).
401 |
402 | **todo-tree.tree.hideIconsWhenGroupedByTag** (`false`)
403 | Hide item icons when grouping by tag.
404 |
405 | **todo-tree.tree.sort** (`true`)
406 | ripgrep searches using multiple threads to improve performance. The tree is sorted when it is populated so that it stays stable. If you want to use ripgrep's own sort arguments, set this to false.
407 |
408 | *Note: Depending on what scan mode you select, you may also want to disable auto-refresh when disabling the sort, otherwise the tree may still be unstable.*
409 |
410 | **todo-tree.tree.disableCompactFolders** (`false`)
411 | The tree will normally respect the VSCode's `explorer.compactFolders` setting. Set this to true if you want to disable compact folders in the todo tree.
412 |
413 | **todo-tree.tree.tooltipFormat** (`${filepath}, ${line}`)
414 | Format of the tree item tooltips. Uses the same placeholders as `todo-tree.tree.labelFormat` (see above).
415 |
416 | **todo-tree.tree.subTagClickUrl**
417 | A URL (which can contain placeholders), which will be opened when clicking on a sub tag in the tree, e.g. `https://github.com/${subTag}` could be used if the sub tag extracts a user name.
418 |
419 | **todo-tree.tree.buttons.reveal** (`true`)
420 | Show a button in the tree view title bar to reveal the current item (only when track file is not enabled).
421 |
422 | **todo-tree.tree.buttons.scanMode** (`false`)
423 | Show a button in the tree view title bar to change the Scan Mode setting.
424 |
425 | **todo-tree.tree.buttons.viewStyle** (`true`)
426 | Show a button in the tree view title bar to change the view style (tree, flat or tags only).
427 |
428 | **todo-tree.tree.buttons.groupByTag** (`true`)
429 | Show a button in the tree view title bar to enable grouping items by tag.
430 |
431 | **todo-tree.tree.buttons.groupBySubTag** (`false`)
432 | Show a button in the tree view title bar to enable grouping items by sub tag.
433 |
434 | *Note: This button will only be visible when sub tags have been found and are present in the tree.*
435 |
436 | **todo-tree.tree.buttons.filter** (`true`)
437 | Show a button in the tree view title bar allowing the tree to be filtered by entering some text.
438 |
439 | **todo-tree.tree.buttons.refresh** (`true`)
440 | Show a refresh button in the tree view title bar.
441 |
442 | **todo-tree.tree.buttons.expand** (`true`)
443 | Show a button in the tree view title bar to expand or collapse the whole tree.
444 |
445 | **todo-tree.tree.buttons.export** (`false`)
446 | Show a button in the tree view title bar to create a text file showing the tree content.
447 |
448 | **Only applies to new workspaces. Once the view has been changed in the workspace, the current state is stored.*
449 |
450 | **todo-tree.filtering.scopes** (`{}`)
451 | Defines a set of file scopes that can be quickly switched between using the *todo-tree.switchScope* command.
452 |
453 | This is a complex configuration property that can only be configured through the configuration JSON file. For example
454 |
455 | ```json
456 | "todo-tree.scopes": [
457 | {
458 | "name": "Production ",
459 | "excludeGlobs": [
460 | "**/tests/**"
461 | ]
462 | },
463 | {
464 | "name": "Tests",
465 | "includeGlobs": [
466 | "**/tests/**"
467 | ]
468 | },
469 | {
470 | "name": "All"
471 | }
472 | ]
473 | ```
474 |
475 | ### Multiline TODOs
476 |
477 | If the regex contains `\n`, then multiline TODOs will be enabled. In this mode, the search results are processed slightly differently. If results are found which do not contain any tags from `todo-tree.general.tags` it will be assumed that they belong to the previous result that did have a tag. For example, if you set the regex to something like:
478 |
479 | ```json
480 | "todo-tree.regex.regex": "(//)\\s*($TAGS).*(\\n\\s*//\\s{2,}.*)*"
481 | ```
482 |
483 | This will now match multiline TODOs where the extra lines have at least two spaces between the comment characters and the TODO item. e.g.
484 |
485 | ```json
486 | // TODO multiline example
487 | // second line
488 | // third line
489 | ```
490 |
491 | If you want to match multiline TODOs in C++ style multiline comment blocks, you'll need something like:
492 |
493 | ```json
494 | "todo-tree.regex.regex": "(/\\*)\\s*($TAGS).*(\\n\\s*(//|/\\*|\\*\\*)\\s{2,}.*)*"
495 | ```
496 |
497 | which should match:
498 |
499 | ```json
500 | /* TODO multiline example
501 | ** second line
502 | ** third line
503 | */
504 | ```
505 |
506 | *Note: If you are modifying settings using the settings GUI, you don't need to escape each backslash.*
507 |
508 | **Warning: Multiline TODOs will not work with markdown TODOs and may have other unexpected results. There may also be a reduction in performance.**
509 |
510 | ### Excluding files and folders
511 |
512 | To restrict the set of folders which is searched, you can define `todo-tree.filtering.includeGlobs`. This is an array of globs which the search results are matched against. If the results match any of the globs, they will be shown. By default the array is empty, which matches everything. See [here](https://code.visualstudio.com/api/references/vscode-api#GlobPattern) for more information on globs.
513 |
514 | *Note: globs paths are absolute - not relative to the current workspace.*
515 |
516 | To exclude folders/files from your search you can define `todo-tree.filtering.excludeGlobs`. If the search results match any of these globs, then the results will be ignored.
517 |
518 | You can also include and exclude folders from the tree using the context menu. This folder filter is applied separately to the include/exclude globs.
519 |
520 | *Note: By default, ripgrep ignores files and folders from your `.gitignore` or `.ignore` files. If you want to include these files, set* `todo-tree.ripgrep.ripgrepArgs` *to* `--no-ignore`.
521 |
522 | ### Markdown Support
523 |
524 | When the extension was first written, very basic markdown support was added simply by adding a pattern to the default regex to match "`- [ ]`". A better way to handle markdown TODOs is to add "`(-|\d+.)`" to the list of "comments" in the first part of the regex and then adding "`[ ]`" and "`[x]`" to the list of tags in `settings.json`, e.g. :
525 |
526 | ```json
527 | "todo-tree.regex.regex": "(//|#|", "x.html" ), " a " );
57 | assert.equal( utils.removeBlockComments( " /* a */", "x.cpp" ), " a " );
58 | assert.equal( utils.removeBlockComments( "b /* a */", "x.cpp" ), "b /* a */" );
59 | assert.equal( utils.removeBlockComments( "{- a -}", "x.hs" ), " a " );
60 | assert.equal( utils.removeBlockComments( "{- a\nb -}", "x.hs" ), " a\nb " );
61 | } );
62 |
63 | QUnit.test( "utils.extractTag removes everything including tag", function( assert )
64 | {
65 | var testConfig = stubs.getTestConfig();
66 | testConfig.shouldGroupByTagFlag = true;
67 | utils.init( testConfig );
68 |
69 | var result = utils.extractTag( "before TODO after" );
70 | assert.equal( result.tag, "TODO" );
71 | assert.equal( result.withoutTag, "after" );
72 | } );
73 |
74 | QUnit.test( "utils.extractTag can be case sensitive", function( assert )
75 | {
76 | var testConfig = stubs.getTestConfig();
77 | testConfig.shouldGroupByTagFlag = true;
78 | testConfig.shouldBeCaseSensitive = false;
79 | utils.init( testConfig );
80 |
81 | var result = utils.extractTag( "before todo after" );
82 | assert.equal( result.tag, "TODO" );
83 | assert.equal( result.withoutTag, "after" );
84 |
85 | testConfig.shouldBeCaseSensitive = true;
86 | result = utils.extractTag( "before todo after" );
87 | assert.equal( result.tag, "" );
88 | assert.equal( result.withoutTag, "before todo after" );
89 | } );
90 |
91 | QUnit.test( "utils.extractTag returns tag from tags list, not the match", function( assert )
92 | {
93 | var testConfig = stubs.getTestConfig();
94 | testConfig.shouldGroupByTagFlag = true;
95 | utils.init( testConfig );
96 |
97 | var result = utils.extractTag( "before todo after" );
98 | assert.equal( result.tag, "TODO" );
99 | assert.equal( result.withoutTag, "after" );
100 | } );
101 |
102 | QUnit.test( "utils.extractTag returns the tag offset", function( assert )
103 | {
104 | var testConfig = stubs.getTestConfig();
105 | testConfig.shouldGroupByTagFlag = true;
106 | utils.init( testConfig );
107 |
108 | var result = utils.extractTag( "before todo after" );
109 | assert.equal( result.tag, "TODO" );
110 | assert.equal( result.tagOffset, 7 );
111 | } );
112 |
113 | QUnit.test( "utils.extractTag removes colon from ${after}", function( assert )
114 | {
115 | var testConfig = stubs.getTestConfig();
116 | utils.init( testConfig );
117 |
118 | result = utils.extractTag( "before TODO: after" );
119 | assert.equal( result.withoutTag, "after" );
120 |
121 | result = utils.extractTag( "before TODO : after" );
122 | assert.equal( result.withoutTag, "after" );
123 |
124 | result = utils.extractTag( "before TODO :after" );
125 | assert.equal( result.withoutTag, "after" );
126 | result = utils.formatLabel( "${tag}: ${after}", { actualTag: result.tag, after: result.withoutTag } );
127 | assert.equal( result, "TODO: after" );
128 | } );
129 |
130 | QUnit.test( "utils.extractTag removes custom regex from ${after}", function( assert )
131 | {
132 | var testConfig = stubs.getTestConfig();
133 | testConfig.subTagRegexString = "(^--\\s*)";
134 | utils.init( testConfig );
135 |
136 | result = utils.extractTag( "before TODO-- after" );
137 | assert.equal( result.withoutTag, "after" );
138 |
139 | result = utils.extractTag( "before TODO -- after" );
140 | assert.equal( result.withoutTag, "after" );
141 |
142 | result = utils.extractTag( "before TODO --after" );
143 | assert.equal( result.withoutTag, "after" );
144 | result = utils.formatLabel( "${tag}-- ${after}", { actualTag: result.tag, after: result.withoutTag } );
145 | assert.equal( result, "TODO-- after" );
146 | } );
147 |
148 | QUnit.test( "utils.extractTag returns text from the start of the line if the tag is on then end", function( assert )
149 | {
150 | var testConfig = stubs.getTestConfig();
151 | utils.init( testConfig );
152 |
153 | result = utils.extractTag( "before TODO" );
154 | assert.equal( result.withoutTag, "before" );
155 | assert.equal( result.before, "before" );
156 | assert.equal( result.after, "" );
157 | } );
158 |
159 | QUnit.test( "utils.extractTag returns text from before and after the tag", function( assert )
160 | {
161 | var testConfig = stubs.getTestConfig();
162 | utils.init( testConfig );
163 |
164 | result = utils.extractTag( " before = text; // TODO stuff ", 32 );
165 | assert.equal( result.withoutTag, "stuff" );
166 | assert.equal( result.before, "before = text;" );
167 | assert.equal( result.after, "stuff" );
168 | } );
169 |
170 | QUnit.test( "utils.extractTag copes with escaped regex characters", function( assert )
171 | {
172 | var testConfig = stubs.getTestConfig();
173 | testConfig.tagList = [
174 | "TO\\DO",
175 | ];
176 | utils.init( testConfig );
177 |
178 | result = utils.extractTag( "before TO\\DO" );
179 | assert.equal( result.tag, "TO\\DO" );
180 | } );
181 |
182 | QUnit.test( "utils.extractTag returns entire text if regex is empty", function( assert )
183 | {
184 | var testConfig = stubs.getTestConfig();
185 | testConfig.regexSource = "";
186 | utils.init( testConfig );
187 |
188 | result = utils.extractTag( " before = text; // TODO stuff ", 1 );
189 | assert.equal( result.withoutTag, " before = text; // TODO stuff " );
190 | assert.equal( result.before, " before = text; // TODO stuff " );
191 | assert.equal( result.after, " before = text; // TODO stuff " );
192 | } );
193 |
194 | QUnit.test( "utils.extractTag returns expected result if regex does not contain $TAGS", function( assert )
195 | {
196 | var testConfig = stubs.getTestConfig();
197 | testConfig.regexSource = "// TODO";
198 | utils.init( testConfig );
199 |
200 | result = utils.extractTag( " before = text; // TODO stuff ", 1 );
201 | assert.equal( result.withoutTag, " stuff " );
202 | assert.equal( result.before, " before = text; " );
203 | assert.equal( result.after, " stuff " );
204 | } );
205 |
206 | QUnit.test( "utils.extractTag can extract sub tag", function( assert )
207 | {
208 | var testConfig = stubs.getTestConfig();
209 | testConfig.subTagRegexString = ".*\\((.*)\\).*";
210 | utils.init( testConfig );
211 |
212 | result = utils.extractTag( "before TODO (email@place.com) after" );
213 | assert.equal( result.subTag, "email@place.com" );
214 | } );
215 |
216 | QUnit.test( "utils.extractTag works with multiline", function( assert )
217 | {
218 | var testConfig = stubs.getTestConfig();
219 | utils.init( testConfig );
220 |
221 | var result = utils.extractTag( "before\nTODO\nafter" );
222 | assert.equal( result.tag, "TODO" );
223 | assert.equal( result.withoutTag, "after" );
224 | } );
225 |
226 | QUnit.test( "utils.getRegexSource returns the regex source without expanded tags if they aren't present", function( assert )
227 | {
228 | var testConfig = stubs.getTestConfig();
229 | testConfig.regexSource = "notags";
230 | utils.init( testConfig );
231 |
232 | assert.equal( utils.getRegexSource(), "notags" );
233 | } );
234 |
235 | QUnit.test( "utils.getRegexSource returns the regex source with expanded tags", function( assert )
236 | {
237 | var testConfig = stubs.getTestConfig();
238 | testConfig.tagList = [ "TWO", "ONE" ];
239 | utils.init( testConfig );
240 |
241 | assert.equal( utils.getRegexSource(), "(TWO|ONE)" );
242 |
243 | testConfig.regexSource = "($TAGS)-($TAGS)";
244 | utils.init( testConfig );
245 |
246 | assert.equal( utils.getRegexSource(), "(TWO|ONE)-(TWO|ONE)" );
247 | } );
248 |
249 | QUnit.test( "utils.getRegexSource returns the regex source and escapes backslashes", function( assert )
250 | {
251 | var testConfig = stubs.getTestConfig();
252 | testConfig.tagList = [ "\\TWO", "ONE\\" ];
253 | utils.init( testConfig );
254 |
255 | assert.equal( utils.getRegexSource(), "(\\\\\\TWO|ONE\\\\\\)" );
256 | } );
257 |
258 | QUnit.test( "utils.getRegexSource sorts the tags in reverse order to allow more specific tags to be found first", function( assert )
259 | {
260 | var testConfig = stubs.getTestConfig();
261 | testConfig.tagList = [ "FIXME", "TODO", "TODO(API)" ];
262 | utils.init( testConfig );
263 |
264 | assert.equal( utils.getRegexSource(), "(TODO\\(API\\)|TODO|FIXME)" );
265 | } );
266 |
267 | QUnit.test( "utils.getRegexSource returns the regex source and escapes other regex characters", function( assert )
268 | {
269 | var testConfig = stubs.getTestConfig();
270 | testConfig.tagList = [
271 | "A|B", "A{B", "A^B", "A[B", "A?B", "A.B", "A+B", "A*B", "A)B", "A(B", "A$B",
272 | ];
273 |
274 | utils.init( testConfig );
275 |
276 | assert.equal( utils.getRegexSource(), "(A\\|B|A\\{B|A\\^B|A\\[B|A\\?B|A\\.B|A\\+B|A\\*B|A\\)B|A\\(B|A\\$B)" );
277 | } );
278 |
279 | QUnit.test( "utils.getRegexForRipGrep applies the expected default flags", function( assert )
280 | {
281 | var testConfig = stubs.getTestConfig();
282 | utils.init( testConfig );
283 | assert.equal( utils.getRegexForRipGrep().flags, "gim" );
284 | } );
285 |
286 | QUnit.test( "utils.getRegexForRipGrep can remove the case insensitive flag", function( assert )
287 | {
288 | var testConfig = stubs.getTestConfig();
289 | testConfig.shouldBeCaseSensitive = true;
290 | utils.init( testConfig );
291 | assert.equal( utils.getRegexForRipGrep().flags, "gm" );
292 | } );
293 |
294 | QUnit.test( "utils.isIncluded returns true when no includes or excludes are specified", function( assert )
295 | {
296 | assert.ok( utils.isIncluded( "filename.js", [], [] ) === true );
297 | assert.ok( utils.isIncluded( "filename.txt", [], [] ) === true );
298 | } );
299 |
300 | QUnit.test( "utils.isIncluded returns false when name matches excludes", function( assert )
301 | {
302 | assert.ok( utils.isIncluded( "filename.js", [], [ "*.txt" ] ) === true );
303 | assert.ok( utils.isIncluded( "filename.txt", [], [ "*.txt" ] ) === false );
304 | } );
305 |
306 | QUnit.test( "utils.isIncluded returns false when name doesn't match includes", function( assert )
307 | {
308 | assert.ok( utils.isIncluded( "filename.js", [ "*.txt" ], [] ) === false );
309 | assert.ok( utils.isIncluded( "filename.txt", [ "*.txt" ], [] ) === true );
310 | } );
311 |
312 | QUnit.test( "utils.isIncluded returns false when name matches includes but also matches excludes", function( assert )
313 | {
314 | assert.ok( utils.isIncluded( "filename.js", [ "*.txt" ], [ "*.js" ] ) === false );
315 | assert.ok( utils.isIncluded( "filename.txt", [ "*.txt" ], [ "*.txt" ] ) === false );
316 | assert.ok( utils.isIncluded( "filename.js", [ "*.txt" ], [ "*.txt" ] ) === false );
317 | assert.ok( utils.isIncluded( "filename.js", [ "*.txt", "*.js" ], [ "*.txt" ] ) === true );
318 | } );
319 |
320 | QUnit.test( "utils.formatLabel replaces line number placeholder", function( assert )
321 | {
322 | var unexpectedPlaceholders = [];
323 | assert.equal( utils.formatLabel( "Label ${line} content", { line: 23 }, unexpectedPlaceholders ), "Label 24 content" ); // line is zero based
324 | assert.equal( unexpectedPlaceholders.length, 0 );
325 | } );
326 |
327 | QUnit.test( "utils.formatLabel replaces column number placeholder", function( assert )
328 | {
329 | var unexpectedPlaceholders = [];
330 | assert.equal( utils.formatLabel( "Label ${column} content", { column: 78 }, unexpectedPlaceholders ), "Label 78 content" );
331 | assert.equal( unexpectedPlaceholders.length, 0 );
332 | } );
333 |
334 | QUnit.test( "utils.formatLabel replaces before text placeholder", function( assert )
335 | {
336 | var unexpectedPlaceholders = [];
337 | assert.equal( utils.formatLabel( "Label ${before} content", { before: "text before tag" }, unexpectedPlaceholders ), "Label text before tag content" );
338 | assert.equal( unexpectedPlaceholders.length, 0 );
339 | } );
340 |
341 | QUnit.test( "utils.formatLabel replaces tag placeholders", function( assert )
342 | {
343 | var unexpectedPlaceholders = [];
344 | assert.equal( utils.formatLabel( "Label ${tag} content", { actualTag: "Todo" }, unexpectedPlaceholders ), "Label Todo content" );
345 | assert.equal( utils.formatLabel( "Label ${tag:uppercase} content", { actualTag: "todo" }, unexpectedPlaceholders ), "Label TODO content" );
346 | assert.equal( utils.formatLabel( "Label ${tag:lowercase} content", { actualTag: "TODO" }, unexpectedPlaceholders ), "Label todo content" );
347 | assert.equal( utils.formatLabel( "Label ${tag:capitalize} content", { actualTag: "todo" }, unexpectedPlaceholders ), "Label Todo content" );
348 | assert.equal( unexpectedPlaceholders.length, 0 );
349 | } );
350 |
351 | QUnit.test( "utils.formatLabel replaces sub tag placeholders", function( assert )
352 | {
353 | var unexpectedPlaceholders = [];
354 | assert.equal( utils.formatLabel( "Label ${subTag} content", { subTag: "name@mail.com" }, unexpectedPlaceholders ), "Label name@mail.com content" );
355 | assert.equal( utils.formatLabel( "Label ${subtag} content", { subTag: "Name@mail.com" }, unexpectedPlaceholders ), "Label Name@mail.com content" );
356 | assert.equal( utils.formatLabel( "Label ${subtag:uppercase} content", { subTag: "example" }, unexpectedPlaceholders ), "Label EXAMPLE content" );
357 | assert.equal( utils.formatLabel( "Label ${subtag:lowercase} content", { subTag: "EXAMPLE" }, unexpectedPlaceholders ), "Label example content" );
358 | assert.equal( utils.formatLabel( "Label ${subtag:capitalize} content", { subTag: "example" }, unexpectedPlaceholders ), "Label Example content" );
359 | assert.equal( unexpectedPlaceholders.length, 0 );
360 | } );
361 |
362 | QUnit.test( "utils.formatLabel replaces after text placeholder", function( assert )
363 | {
364 | var unexpectedPlaceholders = [];
365 | assert.equal( utils.formatLabel( "Label ${after} content", { after: "text after tag" }, unexpectedPlaceholders ), "Label text after tag content" );
366 | assert.equal( unexpectedPlaceholders.length, 0 );
367 | } );
368 |
369 | QUnit.test( "utils.formatLabel replaces before text placeholder", function( assert )
370 | {
371 | var unexpectedPlaceholders = [];
372 | assert.equal( utils.formatLabel( "Label ${before} content", { before: "text before tag" }, unexpectedPlaceholders ), "Label text before tag content" );
373 | assert.equal( unexpectedPlaceholders.length, 0 );
374 | } );
375 |
376 | QUnit.test( "utils.formatLabel replaces filename placeholder", function( assert )
377 | {
378 | var unexpectedPlaceholders = [];
379 | assert.equal( utils.formatLabel( "Label ${filename} content", { fsPath: "/path/to/filename.txt" }, unexpectedPlaceholders ), "Label filename.txt content" );
380 | assert.equal( unexpectedPlaceholders.length, 0 );
381 | } );
382 |
383 | QUnit.test( "utils.formatLabel replaces filepath placeholder", function( assert )
384 | {
385 | var unexpectedPlaceholders = [];
386 | assert.equal( utils.formatLabel( "Label ${filepath} content", { fsPath: "/path/to/filename.txt" }, unexpectedPlaceholders ), "Label /path/to/filename.txt content" );
387 | assert.equal( unexpectedPlaceholders.length, 0 );
388 | } );
389 |
390 | QUnit.test( "utils.formatLabel doesn't report errors if fileName or filePath is undefined", function( assert )
391 | {
392 | var unexpectedPlaceholders = [];
393 | assert.equal( utils.formatLabel( "Label ${filepath} content", {}, unexpectedPlaceholders ), "Label content" );
394 | assert.equal( unexpectedPlaceholders.length, 0 );
395 | assert.equal( utils.formatLabel( "Label ${filename} content", {}, unexpectedPlaceholders ), "Label content" );
396 | assert.equal( unexpectedPlaceholders.length, 0 );
397 | } );
398 |
399 | QUnit.test( "utils.formatLabel reports unexpectedPlaceholders for unexpected placeholders", function( assert )
400 | {
401 | var unexpectedPlaceholders = [];
402 | assert.equal( utils.formatLabel( "Label ${unknown} content", {}, unexpectedPlaceholders ), "Label ${unknown} content" );
403 | assert.equal( unexpectedPlaceholders.length, 1 );
404 | assert.equal( unexpectedPlaceholders[ 0 ], "${unknown}" );
405 | } );
406 |
407 | QUnit.test( "utils.hexToRgba converts correctly", function( assert )
408 | {
409 | assert.equal( utils.hexToRgba( "#000000", 0 ), "rgba(0,0,0,0)" );
410 | assert.equal( utils.hexToRgba( "#000000", 100 ), "rgba(0,0,0,1)" );
411 | assert.equal( utils.hexToRgba( "#000000", 50 ), "rgba(0,0,0,0.5)" );
412 | assert.equal( utils.hexToRgba( "#FFFFFF", 50 ), "rgba(255,255,255,0.5)" );
413 | assert.equal( utils.hexToRgba( "#4080A0", 50 ), "rgba(64,128,160,0.5)" );
414 | assert.equal( utils.hexToRgba( "#fff", 50 ), "rgba(255,255,255,0.5)" );
415 | assert.equal( utils.hexToRgba( "#48a", 50 ), "rgba(68,136,170,0.5)" );
416 | assert.equal( utils.hexToRgba( "#FFFFFFFF", 0 ), "rgba(255,255,255,1)" );
417 | assert.equal( utils.hexToRgba( "#FFFF", 0 ), "rgba(255,255,255,1)" );
418 | assert.equal( utils.hexToRgba( "#FFFFFF80", 0 ), "rgba(255,255,255,0.5)" );
419 | assert.equal( utils.hexToRgba( "#FFF8", 0 ), "rgba(255,255,255,0.53)" );
420 | } );
421 |
422 | QUnit.test( "utils.createFolderGlob creates expected globs", function( assert )
423 | {
424 | if( process.platform === 'win32' )
425 | {
426 | assert.equal( utils.createFolderGlob( "c:\\Users\\name\\workspace\\project\\folder\\subfolder", "c:\\Users\\name\\workspace\\project", "/**/*" ), "**/project/folder/subfolder/**/*" );
427 | assert.equal( utils.createFolderGlob( "c:\\Users\\name\\workspace\\project\\folder\\subfolder", "c:\\Users\\name\\workspace\\project", "/**//*" ), "**/project/folder/subfolder/**/*" );
428 | assert.equal( utils.createFolderGlob( "c:\\folder", "c:\\", "/**/*" ), "**/folder/**/*" );
429 | }
430 | else
431 | {
432 | assert.equal( utils.createFolderGlob( "/Users/name/workspace/project/folder/subfolder", "/Users/name/workspace/project", "/**/*" ), "/Users/name/workspace/project/folder/subfolder/**/*" );
433 | assert.equal( utils.createFolderGlob( "/Users/name/workspace/project/folder/subfolder", "/Users/name/workspace/project", "/**//*" ), "/Users/name/workspace/project/folder/subfolder/**/*" );
434 | }
435 | } );
436 |
437 | QUnit.test( "utils.removeBlockComments supports jsonc", function( assert )
438 | {
439 | assert.equal( utils.removeBlockComments( "/* a */", "x.jsonc" ), " a " );
440 | } );
441 |
442 | QUnit.test( "utils.isHidden", function( assert )
443 | {
444 | assert.equal( utils.isHidden( "test.txt" ), false );
445 | assert.equal( utils.isHidden( "test" ), false );
446 | assert.equal( utils.isHidden( ".test" ), true );
447 | assert.equal( utils.isHidden( "/folder.with.dots/test" ), false );
448 | } );
449 |
450 | QUnit.test( "utils.formatExportPath inserts date and time fields", function( assert )
451 | {
452 | var expectedDateTime = strftime( "%F-%T", new Date( 1307472705067 ) );
453 | assert.equal( utils.formatExportPath( "todo-tree-%F-%T", new Date( 1307472705067 ) ), "todo-tree-" + expectedDateTime );
454 | expectedDateTime = strftime( "%Y%m%d-%H%M", new Date( 1307472705067 ) );
455 | assert.equal( utils.formatExportPath( "todo-tree-%Y%m%d-%H%M-export", new Date( 1307472705067 ) ), "todo-tree-" + expectedDateTime + "-export" );
456 | } );
457 |
458 | QUnit.test( "utils.expandTilde replaces tilde with home folder", function( assert )
459 | {
460 | var homeFolder = os.homedir();
461 | assert.equal( utils.expandTilde( "~/" ), homeFolder + "/" );
462 | } );
463 |
464 | QUnit.test( "utils.formatExportPath expands tilde", function( assert )
465 | {
466 | var homeFolder = os.homedir();
467 | var expectedDateTime = strftime( "%A", new Date( 1307472705067 ) );
468 | assert.equal( utils.formatExportPath( "~/todo-tree-%A", new Date( 1307472705067 ) ), homeFolder + "/todo-tree-" + expectedDateTime );
469 | } );
470 |
471 | QUnit.test( "utils.replaceEnvironmentVariables", function( assert )
472 | {
473 | assert.equal( utils.replaceEnvironmentVariables( "cod, ${FISH}, halibut, ${FISH}" ), "cod, , halibut, " );
474 | process.env.FISH = 'turbot';
475 | assert.equal( utils.replaceEnvironmentVariables( "cod, ${FISH}, halibut, ${FISH}" ), "cod, turbot, halibut, turbot" );
476 | } );
477 |
478 | QUnit.test( "utils.complementaryColour", function( assert )
479 | {
480 | assert.equal( utils.complementaryColour( "#ffffff" ), "#000000" );
481 | assert.equal( utils.complementaryColour( "#ffff00" ), "#000000" );
482 | assert.equal( utils.complementaryColour( "#ff0000" ), "#000000" );
483 | assert.equal( utils.complementaryColour( "#000000" ), "#ffffff" );
484 | assert.equal( utils.complementaryColour( "#00ffff" ), "#000000" );
485 | assert.equal( utils.complementaryColour( "#0000ff" ), "#ffffff" );
486 | } );
487 |
488 | QUnit.test( "utils.isValidColour", function( assert )
489 | {
490 | assert.ok( utils.isValidColour( "red" ) );
491 | assert.ok( utils.isValidColour( "chartreuse" ) );
492 | assert.ok( utils.isValidColour( "Chartreuse" ) );
493 | assert.ok( utils.isValidColour( "#ffffff" ) );
494 | assert.ok( utils.isValidColour( "ffffff" ) );
495 | assert.ok( utils.isValidColour( "rgb(0,0,0)" ) );
496 | assert.ok( utils.isValidColour( "editor.foreground" ) );
497 | assert.notOk( utils.isValidColour( "skybluepink" ) );
498 | assert.notOk( utils.isValidColour( "gggggg" ) );
499 | assert.notOk( utils.isValidColour( "some.theme.colour" ) );
500 | assert.notOk( utils.isValidColour( "rgb(0,0,0" ) );
501 | assert.notOk( utils.isValidColour( "" ) );
502 | } );
503 |
504 | QUnit.test( "utils.setRgbAlpha", function( assert )
505 | {
506 | assert.equal( utils.setRgbAlpha( "fail", 0.5 ), "fail" );
507 | assert.equal( utils.setRgbAlpha( "rgb(0,0,0)", 0.5 ), "rgba(0,0,0,0.5)" );
508 | assert.equal( utils.setRgbAlpha( "rgba(0,0,0,1.0)", 0.5 ), "rgba(0,0,0,0.5)" );
509 | } );
510 |
511 | QUnit.test( "attributes.getForeground uses colour scheme", function( assert )
512 | {
513 | var testConfig = stubs.getTestConfig();
514 | testConfig.useColourScheme = true;
515 | testConfig.tagList = [
516 | "FIXME",
517 | "TODO",
518 | "BUG"
519 | ];
520 | testConfig.foregroundColours = [ "red", "green" ];
521 | attributes.init( testConfig );
522 |
523 | assert.equal( attributes.getForeground( "FIXME" ), "red" );
524 | assert.equal( attributes.getForeground( "TODO" ), "green" );
525 | assert.equal( attributes.getForeground( "BUG" ), "red" );
526 | } );
527 |
528 | QUnit.test( "attributes.getBackground uses colour scheme", function( assert )
529 | {
530 | var testConfig = stubs.getTestConfig();
531 | testConfig.useColourScheme = true;
532 | testConfig.tagList = [
533 | "FIXME",
534 | "TODO",
535 | "BUG"
536 | ];
537 | testConfig.backgroundColours = [ "white", "#000" ];
538 | attributes.init( testConfig );
539 |
540 | assert.equal( attributes.getBackground( "FIXME" ), "white" );
541 | assert.equal( attributes.getBackground( "TODO" ), "#000" );
542 | assert.equal( attributes.getBackground( "BUG" ), "white" );
543 | } );
544 |
545 | QUnit.test( "utils.formatLabel replaces afterOrBefore tag", function( assert )
546 | {
547 | var unexpectedPlaceholders = [];
548 | assert.equal( utils.formatLabel( "${afterOrBefore}", { after: "xxx", before: "yyy" }, unexpectedPlaceholders ), "xxx" );
549 | assert.equal( utils.formatLabel( "${afterOrBefore}", { after: "", before: "yyy" }, unexpectedPlaceholders ), "yyy" );
550 | assert.equal( unexpectedPlaceholders.length, 0 );
551 | } );
552 |
553 | QUnit.test( "utils.isCodicon", function( assert )
554 | {
555 | assert.equal( utils.isCodicon( "$(beaker)" ), true );
556 | assert.equal( utils.isCodicon( " $(beaker)" ), true );
557 | assert.equal( utils.isCodicon( "beaker" ), false );
558 | } );
559 |
560 | QUnit.test( "searchResults can be added and removed", function( assert )
561 | {
562 | searchResults.add( { uri: "uri" } );
563 | assert.equal( searchResults.count(), 1 );
564 | searchResults.remove( "uri" );
565 | assert.equal( searchResults.count(), 0 );
566 | } );
567 |
568 | QUnit.test( "searchResults can be cleared", function( assert )
569 | {
570 | searchResults.add( { uri: "uri1" } );
571 | searchResults.add( { uri: "uri2" } );
572 | assert.equal( searchResults.count(), 2 );
573 | searchResults.clear();
574 | assert.equal( searchResults.count(), 0 );
575 | } );
576 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require( 'path' );
4 |
5 | /**@type {import('webpack').Configuration}*/
6 | const config = {
7 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
8 |
9 | entry: './src/extension.js', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
10 | output: {
11 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
12 | path: path.resolve( __dirname, 'dist' ),
13 | filename: 'extension.js',
14 | libraryTarget: 'commonjs2',
15 | devtoolModuleFilenameTemplate: '../[resource-path]'
16 | },
17 | devtool: 'source-map',
18 | externals: {
19 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
20 | },
21 | resolve: {
22 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
23 | extensions: [ '.ts', '.js' ]
24 | },
25 | module: {
26 | rules: [
27 | {
28 | test: /\.ts$/,
29 | exclude: /node_modules/,
30 | use: [
31 | {
32 | loader: 'ts-loader'
33 | }
34 | ]
35 | }
36 | ]
37 | }
38 | };
39 | module.exports = config;
40 |
--------------------------------------------------------------------------------