├── .gitattributes ├── images ├── icon.png ├── generate-demo.gif ├── todonukem-dark.png ├── todonukem-light.png ├── todonukem-mono.svg └── todonukem-dark.svg ├── .vscodeignore ├── .gitignore ├── .vscode ├── tasks.json └── launch.json ├── tsconfig.json ├── .npmignore ├── package.nls.json ├── package.nls.de.json ├── todonukem.example.json ├── .all-contributorsrc ├── src ├── extension.ts ├── shared │ ├── utils.ts │ ├── constants.ts │ └── config.ts ├── generator.ts ├── decorators.ts └── viewer.ts ├── LICENSE.md ├── snippets ├── snippets-line-comment.json └── snippets-block-comment.json ├── l10n ├── bundle.l10n.json └── bundle.l10n.de.json ├── package.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolution/todo-nukem-snippet-vscode/HEAD/images/icon.png -------------------------------------------------------------------------------- /images/generate-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolution/todo-nukem-snippet-vscode/HEAD/images/generate-demo.gif -------------------------------------------------------------------------------- /images/todonukem-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolution/todo-nukem-snippet-vscode/HEAD/images/todonukem-dark.png -------------------------------------------------------------------------------- /images/todonukem-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jolution/todo-nukem-snippet-vscode/HEAD/images/todonukem-light.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .history/** 4 | .gitattributes 5 | .gitignore 6 | node_modules/** 7 | *.vsix 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | todonukem.json 2 | 3 | # Default ignore 4 | node_modules 5 | *.vsix 6 | .history 7 | .idea 8 | .DS_Store 9 | out 10 | *.log 11 | .vscode-test 12 | .vscode-test-web 13 | dist 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "compile", 6 | "type": "shell", 7 | "command": "npm run compile", 8 | "problemMatcher": [ 9 | "$tsc" 10 | ] 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "out", 6 | "lib": ["ES2020"], 7 | "sourceMap": false, 8 | "rootDir": "src", 9 | "strict": true, 10 | "skipLibCheck": true 11 | }, 12 | "exclude": ["node_modules", ".vscode-test"] 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 9 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 10 | "preLaunchTask": "compile" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Source files 2 | src/ 3 | tsconfig.json 4 | .vscode/ 5 | .vscode-test/ 6 | .vscode-test-web/ 7 | 8 | # Git files 9 | .git/ 10 | .gitignore 11 | .gitattributes 12 | 13 | # Development files 14 | node_modules/ 15 | *.log 16 | .DS_Store 17 | .history/ 18 | .idea/ 19 | 20 | # CI/CD 21 | .github/ 22 | 23 | # Testing 24 | test/ 25 | *.test.ts 26 | *.spec.ts 27 | 28 | # Misc 29 | *.vsix -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "snippetTodoDescription": "Emmet Snippet for TODO with key-based tags", 3 | "snippetFixmeDescription": "Emmet Snippet for FIXME with key-based tags", 4 | "snippetScopeDescription": "Emmet Snippet for TODO NUKEM Convention: Scoping", 5 | "snippetTbdDescription": "This block is used when a task needs further discussion", 6 | "snippetMhdDescription": "This block is used to specify a deadline for a task", 7 | "openTicketInBrowser": "Open {0} in browser", 8 | "enabled": "Enabled", 9 | "disabled": "Disabled", 10 | "decorationsStatus": "TODO NUKEM Decorations: {0}", 11 | "configReloaded": "TODO NUKEM: Config reloaded" 12 | } 13 | -------------------------------------------------------------------------------- /package.nls.de.json: -------------------------------------------------------------------------------- 1 | { 2 | "snippetTodoDescription": "Emmet Snippet für TODO mit schlüsselbasierten Tags", 3 | "snippetFixmeDescription": "Emmet Snippet für FIXME mit schlüsselbasierten Tags", 4 | "snippetScopeDescription": "Emmet Snippet für TODO NUKEM Konvention: Geltungsbereich", 5 | "snippetTbdDescription": "Dieser Block wird verwendet, wenn eine Aufgabe weitere Diskussion benötigt", 6 | "snippetMhdDescription": "Dieser Block wird verwendet, um eine Frist für eine Aufgabe anzugeben", 7 | "openTicketInBrowser": "Ticket {0} im Browser öffnen", 8 | "enabled": "Aktiviert", 9 | "disabled": "Deaktiviert", 10 | "decorationsStatus": "TODO NUKEM Dekorationen: {0}", 11 | "configReloaded": "TODO NUKEM: Konfiguration neu geladen" 12 | } 13 | -------------------------------------------------------------------------------- /images/todonukem-mono.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /todonukem.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft-07/schema#", 3 | "description": "TODO NUKEM Configuration - Copy this to 'todonukem.json' in your workspace root to customize", 4 | "displayMode": "emoji", 5 | "ticketBaseUrl": "https://your-jira-instance.atlassian.net/browse", 6 | "emojis": { 7 | "priority": { 8 | "low": "🟩", 9 | "medium": "🔶", 10 | "high": "🔴" 11 | }, 12 | "type": { 13 | "feature": "✨", 14 | "fix": "🐛" 15 | }, 16 | "context": { 17 | "design": "🎨", 18 | "doc": "📚", 19 | "test": "🧪", 20 | "perf": "⚡", 21 | "lang": "🌐", 22 | "sec": "🔒", 23 | "update": "🔄", 24 | "optimize": "🛠️", 25 | "review": "👀" 26 | }, 27 | "meta": { 28 | "tbd": "💬", 29 | "scope": "🎯", 30 | "ticket": "🎫", 31 | "until": "📅", 32 | "assignee": "👤", 33 | "author": "✍️", 34 | "version": "🔖", 35 | "docs": "📚", 36 | "blockCommit": "🛑" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "commitType": "docs", 8 | "commitConvention": "angular", 9 | "contributors": [ 10 | { 11 | "login": "pimmok", 12 | "name": "Jochen Simon", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/17846993?v=4", 14 | "profile": "https://jochensimon.com/", 15 | "contributions": [ 16 | "design" 17 | ] 18 | },{ 19 | "login": "juliankasimir", 20 | "name": "Julian Kasimir", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/120172350?v=4", 22 | "profile": "https://github.com/juliankasimir", 23 | "contributions": [ 24 | "ideas", 25 | "code" 26 | ] 27 | } 28 | ], 29 | "contributorsPerLine": 7, 30 | "skipCi": true, 31 | "repoType": "github", 32 | "repoHost": "https://github.com", 33 | "projectName": "todo-nukem-snippet-vscode", 34 | "projectOwner": "jolution" 35 | } 36 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO NUKEM Extension Entry Point 3 | * Coordinates Generator and Viewer Modules 4 | */ 5 | import * as vscode from 'vscode'; 6 | import * as generator from './generator'; 7 | import * as viewer from './viewer'; 8 | import * as decorators from './decorators'; 9 | 10 | export function activate(context: vscode.ExtensionContext) { 11 | try { 12 | // Activate Generator module (command for TODO creation) 13 | generator.activate(context); 14 | 15 | // Activate Viewer module (TreeView with filters) 16 | viewer.activate(context); 17 | 18 | // Activate Decorators module (visual emoji display for keys) 19 | decorators.activate(context); 20 | } catch (error) { 21 | vscode.window.showErrorMessage(`TODO NUKEM activation failed: ${error}`); 22 | } 23 | } 24 | 25 | export function deactivate() { 26 | try { 27 | generator.deactivate(); 28 | viewer.deactivate(); 29 | decorators.deactivate(); 30 | } catch (error) { 31 | // Silent error handling 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jolution 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. 22 | -------------------------------------------------------------------------------- /snippets/snippets-line-comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "TODO NUKEM Emmet Snippets": { 3 | "prefix": ["todo"], 4 | "body": "$LINE_COMMENT TODO: ${1|[low],[medium],[high]|} ${2|[feature],[fix]|} ${3|[design],[doc],[test],[perf],[lang],[sec],[update],[optimize],[review]|} ${0}", 5 | "description": "%snippetTodoDescription%" 6 | }, 7 | "TODO NUKEM Fixme": { 8 | "prefix": ["fixme"], 9 | "body": "$LINE_COMMENT FIXME: ${1|[low],[medium],[high]|} [fix] ${3|[design],[doc],[test],[perf],[lang],[sec],[update],[optimize],[review]|} ${0}", 10 | "description": "%snippetFixmeDescription%" 11 | }, 12 | "TODO NUKEM Scope": { 13 | "prefix": ["todo-scope"], 14 | "body": "[scope: ${TM_FILENAME_BASE/(^|-)([a-z])/${2:/capitalize}/g}]", 15 | "description": "%snippetScopeDescription%" 16 | }, 17 | "TODO NUKEM TBD": { 18 | "prefix": ["todo-tbd"], 19 | "body": "[tbd]", 20 | "description": "%snippetTbdDescription%" 21 | }, 22 | "TODO NUKEM MHD": { 23 | "prefix": ["todo-mhd"], 24 | "body": "[until: ${1:${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}}${0}]", 25 | "description": "%snippetMhdDescription%" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /snippets/snippets-block-comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "TODO NUKEM Emmet Snippets": { 3 | "prefix": ["todo"], 4 | "body": "$BLOCK_COMMENT_START TODO: ${1|[low],[medium],[high]|} ${2|[feature],[fix]|} ${3|[design],[doc],[test],[perf],[lang],[sec],[update],[optimize],[review]|} ${0} $BLOCK_COMMENT_END", 5 | "description": "%snippetTodoDescription%" 6 | }, 7 | "TODO NUKEM Fixme": { 8 | "prefix": ["fixme"], 9 | "body": "$BLOCK_COMMENT_START FIXME: ${1|[low],[medium],[high]|} [fix] ${3|[design],[doc],[test],[perf],[lang],[sec],[update],[optimize],[review]|} ${0} $BLOCK_COMMENT_END", 10 | "description": "%snippetFixmeDescription%" 11 | }, 12 | "TODO NUKEM Scope": { 13 | "prefix": ["todo-scope"], 14 | "body": "[scope: ${TM_FILENAME_BASE/(^|-)([a-z])/${2:/capitalize}/g}]", 15 | "description": "%snippetScopeDescription%" 16 | }, 17 | "TODO NUKEM TBD": { 18 | "prefix": ["todo-tbd"], 19 | "body": "[tbd]", 20 | "description": "%snippetTbdDescription%" 21 | }, 22 | "TODO NUKEM MHD": { 23 | "prefix": ["todo-mhd"], 24 | "body": "[until: ${1:${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}}${0}]", 25 | "description": "%snippetMhdDescription%" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /l10n/bundle.l10n.json: -------------------------------------------------------------------------------- 1 | { 2 | "lowPrio": "Low Prio", 3 | "mediumPrio": "Medium Prio", 4 | "highPrio": "High Prio", 5 | "selectPriority": "Select priority", 6 | "feature": "Feature", 7 | "fix": "Fix", 8 | "selectType": "Select type", 9 | "design": "Design", 10 | "doc": "Doc", 11 | "test": "Test", 12 | "perf": "Perf", 13 | "lang": "Lang", 14 | "sec": "Sec", 15 | "update": "Update", 16 | "optimize": "Optimize", 17 | "review": "Review", 18 | "selectContext": "Select context", 19 | "enterTodoMessage": "Enter your TODO message", 20 | "whatNeedsToBeDone": "What needs to be done?", 21 | "tbd": "TBD", 22 | "scope": "Scope", 23 | "ticket": "Ticket", 24 | "until": "Until", 25 | "assignee": "Assignee", 26 | "selfAssignee": "Self Assign", 27 | "author": "Author", 28 | "selfAuthor": "Self Author", 29 | "version": "Version", 30 | "docs": "Docs", 31 | "blockCommit": "Block-Commit", 32 | "unknown": "Unknown", 33 | "optionalMetaBlocks": "Optional meta blocks", 34 | "enterValueFor": "Enter value for {0}", 35 | "valueLabel": "{0} value", 36 | "selectPriorityFilter": "Select priority to filter", 37 | "selectTypeFilter": "Select type to filter", 38 | "selectContextFilter": "Select context to filter", 39 | "enterAssigneeName": "Enter assignee name (or leave empty for all)", 40 | "enterAuthorName": "Enter author name (or leave empty for all)", 41 | "all": "All", 42 | "exampleName": "e.g. Duke Nukem", 43 | "openTicketInBrowser": "Open {0} in browser", 44 | "enabled": "Enabled", 45 | "disabled": "Disabled", 46 | "decorationsStatus": "TODO NUKEM Decorations: {0}", 47 | "configReloaded": "TODO NUKEM: Config reloaded" 48 | } 49 | -------------------------------------------------------------------------------- /l10n/bundle.l10n.de.json: -------------------------------------------------------------------------------- 1 | { 2 | "lowPrio": "Niedrige Priorität", 3 | "mediumPrio": "Mittlere Priorität", 4 | "highPrio": "Hohe Priorität", 5 | "selectPriority": "Priorität auswählen", 6 | "feature": "Feature", 7 | "fix": "Fix", 8 | "selectType": "Typ auswählen", 9 | "design": "Design", 10 | "doc": "Dokumentation", 11 | "test": "Test", 12 | "perf": "Performance", 13 | "lang": "Sprache", 14 | "sec": "Sicherheit", 15 | "update": "Update", 16 | "optimize": "Optimierung", 17 | "review": "Review", 18 | "selectContext": "Kontext auswählen", 19 | "enterTodoMessage": "Geben Sie Ihre TODO-Nachricht ein", 20 | "whatNeedsToBeDone": "Was muss erledigt werden?", 21 | "tbd": "Zu Klären", 22 | "scope": "Geltungsbereich", 23 | "ticket": "Ticket", 24 | "until": "Bis", 25 | "assignee": "Zugewiesener", 26 | "selfAssignee": "Selbstzuweisung", 27 | "author": "Autor", 28 | "selfAuthor": "Ich als Autor", 29 | "version": "Version", 30 | "docs": "Dokumentation", 31 | "blockCommit": "Commit-Blockierung", 32 | "unknown": "Unbekannt", 33 | "optionalMetaBlocks": "Optionale Meta-Blöcke", 34 | "enterValueFor": "Wert für {0} eingeben", 35 | "valueLabel": "{0} Wert", 36 | "selectPriorityFilter": "Wähle Priorität zum Filtern", 37 | "selectTypeFilter": "Wähle Typ zum Filtern", 38 | "selectContextFilter": "Wähle Kontext zum Filtern", 39 | "enterAssigneeName": "Gib den Assignee-Namen ein (oder leer lassen für alle)", 40 | "enterAuthorName": "Gib den Author-Namen ein (oder leer lassen für alle)", 41 | "all": "Alle", 42 | "exampleName": "z.B. Duke Nukem", 43 | "openTicketInBrowser": "Öffne {0} im Browser", 44 | "enabled": "Aktiviert", 45 | "disabled": "Deaktiviert", 46 | "decorationsStatus": "TODO NUKEM Dekorationen: {0}", 47 | "configReloaded": "TODO NUKEM: Konfiguration neu geladen" 48 | } 49 | -------------------------------------------------------------------------------- /src/shared/utils.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export enum MetaBlockKey { 4 | TBD = 'TBD', 5 | Scope = 'Scope', 6 | Ticket = 'Ticket', 7 | Until = 'Until', 8 | Assignee = 'Assignee', 9 | SelfAssignee = 'SelfAssignee', 10 | Author = 'Author', 11 | SelfAuthor = 'SelfAuthor', 12 | Version = 'Version', 13 | Docs = 'Docs', 14 | BlockCommit = 'Block-Commit' 15 | } 16 | 17 | export interface MetaBlock extends vscode.QuickPickItem { 18 | key: string; 19 | metaType: MetaBlockKey; 20 | } 21 | 22 | /** 23 | * Retrieves the Git username from the current workspace 24 | */ 25 | export function getGitUserName(): string { 26 | try { 27 | const { execSync } = require('child_process'); 28 | return execSync('git config user.name', { 29 | cwd: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath, 30 | encoding: 'utf8' 31 | }).trim(); 32 | } catch { 33 | return ''; 34 | } 35 | } 36 | 37 | /** 38 | * Returns the appropriate comment prefix for the given language 39 | */ 40 | export function getCommentPrefix(languageId: string): string { 41 | const commentMap: { [key: string]: string } = { 42 | 'javascript': '//', 43 | 'typescript': '//', 44 | 'java': '//', 45 | 'c': '//', 46 | 'cpp': '//', 47 | 'csharp': '//', 48 | 'go': '//', 49 | 'rust': '//', 50 | 'php': '//', 51 | 'swift': '//', 52 | 'kotlin': '//', 53 | 'dart': '//', 54 | 'python': '#', 55 | 'ruby': '#', 56 | 'shell': '#', 57 | 'bash': '#', 58 | 'powershell': '#', 59 | 'yaml': '#', 60 | 'perl': '#', 61 | 'r': '#', 62 | 'html': ' 14 | [![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-) 15 | 16 | 17 | **Working Draft** 18 | 19 | A VSCode extension for creating and managing structured TODO comments with emojis following the [TODO NUKEM Convention](https://github.com/jolution/todo-nukem/blob/main/README.md). Features **interactive comment generation**, **TODO overview with filtering**, and **quick snippets** for maximum productivity. 20 | 21 | 22 | 23 | 24 | 25 | 26 | ## 📰 Installation 27 | 28 | Install this extension from the [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=jolution.todo-nukem-vscode) 29 | 30 | ## 🚀 Usage 31 | 32 | This extension offers **two ways** to create TODO NUKEM comments: 33 | 34 | ### 1. 🎯 Interactive Command (Recommended) 35 | 36 | Open the Command Palette (`Cmd+Shift+P` or `Ctrl+Shift+P`) and search for: 37 | 38 | ``` 39 | TODO NUKEM Comment 40 | ``` 41 | 42 | Follow the guided prompts: 43 | 44 | 1. **Priority**: 🟩 Low / 🔶 Medium / 🔴 High 45 | 2. **Type**: ✨ Feature / 🐛 Fix 46 | 3. **Context**: 🎨 Design / 📚 Doc / 🧪 Test / ⚡ Perf / etc. 47 | 4. **Message**: Your TODO description 48 | 5. **Meta Blocks** (optional): 💬 TBD / 🎯 Scope / 🎫 Ticket / 📅 Until / etc. 49 | 50 | **Example in source code:** 51 | 52 | ```typescript 53 | // TODO: [high] [feature] [design] Refactor button component [ticket: JIRA-123] [until: 2025-12-31] 54 | ``` 55 | 56 | **Visual display (with decorations):** 57 | 58 | The extension decorates the keys with emojis in the editor: 59 | 60 | ```text 61 | // TODO: 🔴 ✨ 🎨 Refactor button component 🎫 JIRA-123 📅 2025-12-31 62 | ``` 63 | 64 | > **Note:** You can customize the display mode in `todonukem.json` (emoji, text, or emoji-text combination). Alternatively, click the **eye icon (👁️)** in the status bar to quickly toggle between display modes. 65 | 66 | ### 2. ⚡ Quick Snippets 67 | 68 | In supported languages, type `todo` or `fixme` and press `Tab` to activate snippet templates: 69 | 70 | ```todo ⇥``` 71 | ```fixme ⇥``` 72 | 73 | This provides pre-defined templates for quick TODO insertion. 74 | 75 | ## ✨ Supported Languages 76 | 77 | _Defined in the ```package.json``` file, the following languages are supported with either line or block comments:_ 78 | 79 | ### Line Comment 80 | 81 | ```TypeScript, JavaScript``` 82 | 83 | ### Block Comment 84 | 85 | ```CSS, PostCSS, SCSS, Less, HTML, Python, Java, C#, C++, Ruby, Swift, PHP, Go, Rust, Dart, Perl, Lua, Shell Script``` 86 | 87 | For example, CSS uses block comments like ```/* ... */```. 88 | 89 | And TypeScript could use line comments like ```// ...```. 90 | 91 | **If a language you need is missing, feel free to open a PR and contribute!** 92 | 93 | ## ❓FAQ 94 | 95 |
96 | How can I enable snippet suggestions in comments in VSCode? 97 |

By default, snippet suggestions are not active in comments in VSCode. If you want to enable this feature, you need to adjust your settings. 98 | 99 | In User Settings search for `quickSuggestions` and enable the following options: 100 | 101 | ```json 102 | "editor.quickSuggestions": { 103 | "comments": true, 104 | "strings": true 105 | } 106 | ``` 107 | 108 |

109 |
110 | 111 |
112 | The green emoji (🟩) doesn't display on older Windows 10 versions 113 |

Older Windows 10 versions don't support the green square emoji (🟩). To fix this, create a todonukem.json file in your workspace root with the following content: 114 | 115 | ```json 116 | { 117 | "emojis": { 118 | "priority": { 119 | "low": "🔵" 120 | } 121 | } 122 | } 123 | ``` 124 | 125 | This replaces the green square with a blue circle (🔵). 126 | 127 | After creating the file, press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac), type `reload`, and select **"Developer: Reload Window"** to apply the changes. 128 | 129 |

130 |
131 | 132 |
133 | How can I customize the display mode? 134 |

You can customize how TODOs are displayed by creating a todonukem.json file in your workspace root: 135 | 136 | **Emoji only (default):** 137 | 138 | ```json 139 | { 140 | "displayMode": "emoji" 141 | } 142 | ``` 143 | 144 | Displays: `🔴 ✨ 🎨` 145 | 146 | **Text only:** 147 | 148 | ```json 149 | { 150 | "displayMode": "text" 151 | } 152 | ``` 153 | 154 | Displays: `High Feature Design` 155 | 156 | **Emoji-text combination:** 157 | 158 | ```json 159 | { 160 | "displayMode": "emoji-text" 161 | } 162 | ``` 163 | 164 | Displays: `🔴-high ✨-feature 🎨-design` 165 | 166 | After creating or modifying the file, reload the window with `Ctrl+Shift+P` → `reload` → **"Developer: Reload Window"**. 167 | 168 |

169 |
170 | 171 |
172 | How can I configure ticket links? 173 |

You can make ticket references clickable by configuring a ticketBaseUrl in your todonukem.json file: 174 | 175 | ```json 176 | { 177 | "ticketBaseUrl": "https://jira.example.com/browse" 178 | } 179 | ``` 180 | 181 | Now when you use `[ticket: JIRA-123]` in your TODO comments, the ticket ID becomes a clickable link that opens in your browser: 182 | 183 | ```typescript 184 | // TODO: [high] [feature] Fix login bug [ticket: JIRA-123] 185 | ``` 186 | 187 | Clicking on `JIRA-123` will open `https://jira.example.com/browse/JIRA-123`. 188 | 189 | After creating or modifying the file, reload the window with `Ctrl+Shift+P` → `reload` → **"Developer: Reload Window"**. 190 | 191 |

192 |
193 | 194 | For more questions and answers, please visit our [Q&A Discussions](https://github.com/jolution/todo-nukem/discussions/categories/q-a). 195 | 196 | ## 🔗 Related Tools 197 | 198 | ### ESLint Plugin 199 | 200 | For best results, combine this extension with the **TODO NUKEM ESLint plugin** to enforce the convention in your codebase: 201 | 202 | [![eslint-plugin-todo-nukem](https://img.shields.io/npm/v/eslint-plugin-todo-nukem?label=eslint-plugin-todo-nukem&logo=eslint)](https://github.com/jolution/eslint-plugin-todo-nukem) 203 | 204 | The ESLint plugin validates that your TODO comments follow the TODO NUKEM Convention and can auto-fix formatting issues. 205 | 206 | ```bash 207 | npm install --save-dev @jolution/eslint-plugin-todo-nukem 208 | ``` 209 | 210 | **Learn more:** 211 | [GitHub Repository](https://github.com/jolution/eslint-plugin-todo-nukem) 212 | [npm Package](https://www.npmjs.com/package/@jolution/eslint-plugin-todo-nukem) 213 | 214 | ## ❤️ Support 215 | 216 | If you find this project helpful, please consider giving it the Convention Repo a star on [GitHub](https://github.com/jolution/todo-nukem). 217 | 218 | [![Star this repository](https://img.shields.io/github/stars/jolution/todo-nukem-snippet-vscode?style=social)](https://github.com/jolution/todo-nukem) 219 | 220 | We do not currently offer direct support for this project. 221 | 222 | ## 💎 Sponsor 223 | 224 | ### Atos 225 | 226 | We appreciate the support from [Atos](https://atos.net), helping us continue our open source work. 227 | 228 | ### ✍️ Authors (in alphabetical order) 229 | 230 | - [@juliankasimir](https://www.github.com/juliankasimir) 231 | - [@pimmok](https://www.github.com/pimmok) 232 | 233 | ## ⚖️ License 234 | 235 | See the [LICENSE](LICENSE) file for details. 236 | 237 | ## ℹ️ Disclaimer 238 | 239 | Please note that this project, TODO NUKEM, is not officially associated with or endorsed by the Duke Nukem franchise or its creators. It is an independent project developed by the open-source community and does not claim any rights to the Duke Nukem trademark or any related materials. 240 | 241 | ## ✨ Contributors 242 | 243 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 |
Jochen Simon
Jochen Simon

🎨
Julian Kasimir
Julian Kasimir

🤔 💻
256 | 257 | 258 | 259 | 260 | 261 | 262 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 263 | -------------------------------------------------------------------------------- /src/viewer.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | import { Priority, Type, Context } from './shared/constants'; 5 | import { ignoredDirs, todoFileExtensions } from './shared/config'; 6 | 7 | const packageJson = require('../package.json'); 8 | 9 | export function activate(context: vscode.ExtensionContext) { 10 | const todoProvider = new TodoTreeDataProvider(); 11 | const treeView = vscode.window.createTreeView('todoNukemView', { 12 | treeDataProvider: todoProvider 13 | }); 14 | 15 | // Set title with version (TODO NUKEM before already) 16 | treeView.title = `TODOs v${packageJson.version}`; 17 | 18 | // Command: Show TODOs (focuses TreeView and loads TODOs) 19 | let showTodosCommand = vscode.commands.registerCommand('todoNukem.showTodos', () => { 20 | vscode.commands.executeCommand('todoNukemView.focus'); 21 | todoProvider.refresh(); 22 | }); 23 | 24 | // Command: Refresh TODOs 25 | let refreshCommand = vscode.commands.registerCommand('todoNukem.refresh', () => { 26 | todoProvider.refresh(); 27 | }); 28 | 29 | // Command: Filter by Priority 30 | let filterPriorityCommand = vscode.commands.registerCommand('todoNukem.filterByPriority', async () => { 31 | const priority = await vscode.window.showQuickPick( 32 | [ 33 | { label: FILTER_ALL, key: FILTER_ALL }, 34 | { label: `${Priority.High.emoji} ${vscode.l10n.t('highPrio')}`, key: Priority.High.key }, 35 | { label: `${Priority.Medium.emoji} ${vscode.l10n.t('mediumPrio')}`, key: Priority.Medium.key }, 36 | { label: `${Priority.Low.emoji} ${vscode.l10n.t('lowPrio')}`, key: Priority.Low.key } 37 | ], 38 | { placeHolder: vscode.l10n.t('selectPriorityFilter') } 39 | ); 40 | if (priority) { 41 | todoProvider.setFilterPriority(priority.key); 42 | } 43 | }); 44 | 45 | // Command: Filter by Type 46 | let filterTypeCommand = vscode.commands.registerCommand('todoNukem.filterByType', async () => { 47 | const type = await vscode.window.showQuickPick( 48 | [ 49 | { label: FILTER_ALL, key: FILTER_ALL }, 50 | { label: `${Type.Feature.emoji} ${vscode.l10n.t('feature')}`, key: Type.Feature.key }, 51 | { label: `${Type.Fix.emoji} ${vscode.l10n.t('fix')}`, key: Type.Fix.key } 52 | ], 53 | { placeHolder: vscode.l10n.t('selectTypeFilter') } 54 | ); 55 | if (type) { 56 | todoProvider.setFilterType(type.key); 57 | } 58 | }); 59 | 60 | // Command: Filter by Context 61 | let filterContextCommand = vscode.commands.registerCommand('todoNukem.filterByContext', async () => { 62 | const context = await vscode.window.showQuickPick( 63 | [ 64 | { label: FILTER_ALL, key: FILTER_ALL }, 65 | { label: `${Context.Design.emoji} ${vscode.l10n.t('design')}`, key: Context.Design.key }, 66 | { label: `${Context.Doc.emoji} ${vscode.l10n.t('doc')}`, key: Context.Doc.key }, 67 | { label: `${Context.Test.emoji} ${vscode.l10n.t('test')}`, key: Context.Test.key }, 68 | { label: `${Context.Perf.emoji} ${vscode.l10n.t('perf')}`, key: Context.Perf.key }, 69 | { label: `${Context.Lang.emoji} ${vscode.l10n.t('lang')}`, key: Context.Lang.key }, 70 | { label: `${Context.Sec.emoji} ${vscode.l10n.t('sec')}`, key: Context.Sec.key }, 71 | { label: `${Context.Update.emoji} ${vscode.l10n.t('update')}`, key: Context.Update.key }, 72 | { label: `${Context.Optimize.emoji} ${vscode.l10n.t('optimize')}`, key: Context.Optimize.key }, 73 | { label: `${Context.Review.emoji} ${vscode.l10n.t('review')}`, key: Context.Review.key } 74 | ], 75 | { placeHolder: vscode.l10n.t('selectContextFilter') } 76 | ); 77 | if (context) { 78 | todoProvider.setFilterContext(context.key); 79 | } 80 | }); 81 | 82 | // Command: Filter by Assignee 83 | let filterAssigneeCommand = vscode.commands.registerCommand('todoNukem.filterByAssignee', async () => { 84 | const assignee = await vscode.window.showInputBox({ 85 | placeHolder: vscode.l10n.t('exampleName'), 86 | prompt: vscode.l10n.t('enterAssigneeName'), 87 | value: todoProvider.getFilterAssignee() === FILTER_ALL ? '' : todoProvider.getFilterAssignee() 88 | }); 89 | if (assignee !== undefined) { 90 | todoProvider.setFilterAssignee(assignee || FILTER_ALL); 91 | } 92 | }); 93 | 94 | // Command: Filter by Author 95 | let filterAuthorCommand = vscode.commands.registerCommand('todoNukem.filterByAuthor', async () => { 96 | const author = await vscode.window.showInputBox({ 97 | placeHolder: vscode.l10n.t('exampleName'), 98 | prompt: vscode.l10n.t('enterAuthorName'), 99 | value: todoProvider.getFilterAuthor() === FILTER_ALL ? '' : todoProvider.getFilterAuthor() 100 | }); 101 | if (author !== undefined) { 102 | todoProvider.setFilterAuthor(author || FILTER_ALL); 103 | } 104 | }); 105 | 106 | // Command: Open TODO in file 107 | let openTodoCommand = vscode.commands.registerCommand('todoNukem.openTodo', (todo: TodoItem) => { 108 | const uri = vscode.Uri.file(todo.filePath); 109 | vscode.workspace.openTextDocument(uri).then(doc => { 110 | vscode.window.showTextDocument(doc).then(editor => { 111 | const position = new vscode.Position(todo.line, 0); 112 | editor.selection = new vscode.Selection(position, position); 113 | editor.revealRange(new vscode.Range(position, position), vscode.TextEditorRevealType.InCenter); 114 | }); 115 | }); 116 | }); 117 | 118 | context.subscriptions.push(showTodosCommand, refreshCommand, filterPriorityCommand, filterTypeCommand, filterContextCommand, filterAssigneeCommand, filterAuthorCommand, openTodoCommand, treeView); 119 | todoProvider.refresh(); 120 | } 121 | 122 | export function deactivate() {} 123 | 124 | const FILTER_ALL = vscode.l10n.t('all'); 125 | 126 | interface TodoItem { 127 | text: string; 128 | priority: string | null; 129 | type: string | null; 130 | context: string | null; 131 | assignee: string | null; 132 | author: string | null; 133 | filePath: string; 134 | line: number; 135 | } 136 | 137 | class TodoTreeDataProvider implements vscode.TreeDataProvider { 138 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 139 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 140 | 141 | private todos: TodoItem[] = []; 142 | private filterPriority: string = FILTER_ALL; 143 | private filterType: string = FILTER_ALL; 144 | private filterContext: string = FILTER_ALL; 145 | private filterAssignee: string = FILTER_ALL; 146 | private filterAuthor: string = FILTER_ALL; 147 | 148 | async refresh(): Promise { 149 | this.todos = []; 150 | await this.scanWorkspace(); 151 | this._onDidChangeTreeData.fire(); 152 | } 153 | 154 | setFilterPriority(filter: string): void { 155 | this.filterPriority = filter; 156 | this._onDidChangeTreeData.fire(); 157 | } 158 | 159 | setFilterType(filter: string): void { 160 | this.filterType = filter; 161 | this._onDidChangeTreeData.fire(); 162 | } 163 | 164 | setFilterContext(filter: string): void { 165 | this.filterContext = filter; 166 | this._onDidChangeTreeData.fire(); 167 | } 168 | 169 | getFilterAssignee(): string { 170 | return this.filterAssignee; 171 | } 172 | 173 | setFilterAssignee(filter: string): void { 174 | this.filterAssignee = filter; 175 | this._onDidChangeTreeData.fire(); 176 | } 177 | 178 | getFilterAuthor(): string { 179 | return this.filterAuthor; 180 | } 181 | 182 | setFilterAuthor(filter: string): void { 183 | this.filterAuthor = filter; 184 | this._onDidChangeTreeData.fire(); 185 | } 186 | 187 | getTreeItem(element: TodoTreeItem): vscode.TreeItem { 188 | return element; 189 | } 190 | 191 | getChildren(element?: TodoTreeItem): Thenable { 192 | if (!element) { 193 | return Promise.resolve(this.getFilteredTodos()); 194 | } 195 | return Promise.resolve([]); 196 | } 197 | 198 | private getFilteredTodos(): TodoTreeItem[] { 199 | let filtered = this.todos; 200 | 201 | // Filter by Priority (compare keys directly) 202 | if (this.filterPriority !== FILTER_ALL) { 203 | filtered = filtered.filter(todo => todo.priority === this.filterPriority); 204 | } 205 | 206 | // Filter by Type (compare keys directly) 207 | if (this.filterType !== FILTER_ALL) { 208 | filtered = filtered.filter(todo => todo.type === this.filterType); 209 | } 210 | 211 | // Filter by Context (compare keys directly) 212 | if (this.filterContext !== FILTER_ALL) { 213 | filtered = filtered.filter(todo => todo.context === this.filterContext); 214 | } 215 | 216 | // Filter by Assignee 217 | if (this.filterAssignee !== FILTER_ALL) { 218 | filtered = filtered.filter(todo => 219 | todo.assignee && todo.assignee.toLowerCase().includes(this.filterAssignee.toLowerCase()) 220 | ); 221 | } 222 | 223 | // Filter by Author 224 | if (this.filterAuthor !== FILTER_ALL) { 225 | filtered = filtered.filter(todo => 226 | todo.author && todo.author.toLowerCase().includes(this.filterAuthor.toLowerCase()) 227 | ); 228 | } 229 | 230 | // Sort: High > Medium > Low > no priority 231 | const priorityOrder: { [key: string]: number } = { 232 | '[high]': 1, 233 | '[medium]': 2, 234 | '[low]': 3 235 | }; 236 | 237 | filtered.sort((a, b) => { 238 | const orderA = a.priority ? (priorityOrder[a.priority] || 999) : 999; 239 | const orderB = b.priority ? (priorityOrder[b.priority] || 999) : 999; 240 | return orderA - orderB; 241 | }); 242 | 243 | return filtered.map(todo => new TodoTreeItem(todo)); 244 | } 245 | 246 | private async scanWorkspace(): Promise { 247 | this.todos = []; 248 | 249 | if (!vscode.workspace.workspaceFolders) { 250 | return; 251 | } 252 | 253 | for (const folder of vscode.workspace.workspaceFolders) { 254 | await this.scanDirectory(folder.uri.fsPath); 255 | } 256 | 257 | // Remove duplicates based on filePath and line number 258 | const uniqueTodos = new Map(); 259 | this.todos.forEach(todo => { 260 | const key = `${todo.filePath}:${todo.line}`; 261 | if (!uniqueTodos.has(key)) { 262 | uniqueTodos.set(key, todo); 263 | } 264 | }); 265 | this.todos = Array.from(uniqueTodos.values()); 266 | } 267 | 268 | private async scanDirectory(dirPath: string): Promise { 269 | const entries = await fs.promises.readdir(dirPath, { withFileTypes: true }); 270 | 271 | for (const entry of entries) { 272 | const fullPath = path.join(dirPath, entry.name); 273 | 274 | // Ignore specific directories 275 | if (entry.isDirectory()) { 276 | if (this.shouldIgnoreDirectory(entry.name)) { 277 | continue; 278 | } 279 | await this.scanDirectory(fullPath); 280 | } else if (entry.isFile()) { 281 | if (this.shouldScanFile(entry.name)) { 282 | await this.scanFile(fullPath); 283 | } 284 | } 285 | } 286 | } 287 | 288 | private shouldIgnoreDirectory(name: string): boolean { 289 | return ignoredDirs.includes(name); 290 | } 291 | 292 | private shouldScanFile(name: string): boolean { 293 | return todoFileExtensions.some(ext => name.endsWith(ext)); 294 | } 295 | 296 | private async scanFile(filePath: string): Promise { 297 | try { 298 | const content = await fs.promises.readFile(filePath, 'utf-8'); 299 | const lines = content.split('\n'); 300 | 301 | lines.forEach((line, index) => { 302 | // Find TODO: and extract the rest 303 | const todoMatch = line.match(/TODO:?\s+(.+)/i); 304 | if (todoMatch) { 305 | let fullText = todoMatch[1].trim(); 306 | // Remove trailing */ or --> 307 | fullText = fullText.replace(/(\*\/|-->)\s*$/, '').trim(); 308 | 309 | if (fullText.length > 0) { 310 | // TODO NUKEM Format: [low] [feature] [design] Message [Meta] 311 | // Extract the first 3 keys (Priority, Type, Context) 312 | const parts = fullText; 313 | 314 | // Priority keys 315 | const priorityKeys = Object.values(Priority).map(p => p.key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); 316 | const priorityMatch = parts.match(new RegExp(`^(${priorityKeys.join('|')})`)); 317 | const priority = priorityMatch ? priorityMatch[1] : null; 318 | 319 | // Type keys 320 | const typeKeys = Object.values(Type).map(t => t.key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); 321 | const typeMatch = parts.match(new RegExp(`^(?:${priorityKeys.join('|')})?\\s*(${typeKeys.join('|')})`)); 322 | const type = typeMatch ? typeMatch[1] : null; 323 | 324 | // Context keys 325 | const contextKeys = Object.values(Context).map(c => c.key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); 326 | const contextMatch = parts.match(new RegExp(`^(?:${priorityKeys.join('|')})?\\s*(?:${typeKeys.join('|')})?\\s*(${contextKeys.join('|')})`)); 327 | const context = contextMatch ? contextMatch[1] : null; 328 | 329 | // Extract Assignee from [assignee: value] (colon followed by exactly one space) 330 | const assigneeMatch = parts.match(/\[(?:assignee|👤):\s([^\]]+)\]/); 331 | const assignee = assigneeMatch ? assigneeMatch[1].trim() : null; 332 | 333 | // Extract Author from [author: value] (colon followed by exactly one space) 334 | const authorMatch = parts.match(/\[(?:author|✍️):\s([^\]]+)\]/); 335 | const author = authorMatch ? authorMatch[1].trim() : null; 336 | 337 | this.todos.push({ 338 | text: fullText, 339 | priority: priority, 340 | type: type, 341 | context: context, 342 | assignee: assignee, 343 | author: author, 344 | filePath: filePath, 345 | line: index 346 | }); 347 | } 348 | } 349 | }); 350 | } catch (error) { 351 | // Silent error handling for file scanning 352 | } 353 | } 354 | } 355 | 356 | class TodoTreeItem extends vscode.TreeItem { 357 | constructor(public readonly todo: TodoItem) { 358 | const fileName = path.basename(todo.filePath); 359 | 360 | super(todo.text, vscode.TreeItemCollapsibleState.None); 361 | 362 | this.description = `${fileName}:${todo.line + 1}`; 363 | this.tooltip = `${todo.filePath}\nLine: ${todo.line + 1}\n\n${todo.text}`; 364 | 365 | this.command = { 366 | command: 'todoNukem.openTodo', 367 | title: 'Open TODO', 368 | arguments: [todo] 369 | }; 370 | 371 | // Icon based on priority 372 | if (todo.priority === Priority.High.key) { 373 | this.iconPath = new vscode.ThemeIcon('error', new vscode.ThemeColor('errorForeground')); 374 | } else if (todo.priority === Priority.Medium.key) { 375 | this.iconPath = new vscode.ThemeIcon('warning', new vscode.ThemeColor('editorWarning.foreground')); 376 | } else if (todo.priority === Priority.Low.key) { 377 | this.iconPath = new vscode.ThemeIcon('pass', new vscode.ThemeColor('testing.iconPassed')); 378 | } else { 379 | this.iconPath = new vscode.ThemeIcon('comment'); 380 | } 381 | 382 | this.contextValue = 'todoItem'; 383 | } 384 | } 385 | --------------------------------------------------------------------------------