├── icon.png ├── videos ├── comment.gif ├── addProject.PNG ├── cliplistcom.gif ├── clippropdic.gif ├── classInterface.PNG ├── createproject.PNG └── clipvarclassfunc.gif ├── media ├── addProjectIcon.png ├── reset.css ├── sdks.txt ├── index.html ├── vscode.css ├── addProject.js └── main.js ├── models ├── class6.mdl ├── interface6.mdl ├── record6.mdl ├── struct6.mdl ├── class.mdl ├── record.mdl ├── struct.mdl └── interface.mdl ├── .vscodeignore ├── .vscode ├── extensions.json ├── tasks.json ├── launch.json └── settings.json ├── .gitignore ├── src ├── resource │ ├── createProjectWebView │ │ ├── GetNonce.ts │ │ └── CreateProject.ts │ ├── smartComments │ │ ├── SmartComments.ts │ │ └── Parser.ts │ ├── addProjectToSolution │ │ ├── AddProjectToSolution.ts │ │ └── Panel.ts │ ├── todoList │ │ └── icon.svg │ └── contextualMenu │ │ └── ContextualMenu.ts ├── extension.ts ├── utils │ ├── sdk.provider.ts │ └── terminal-cmd.provider.ts └── CommandRegister.ts ├── tsconfig.json ├── .eslintrc.json ├── LICENSE.md ├── snippets ├── designpattern.json ├── documentxml.json └── general.json ├── CHANGELOG.md ├── package.json └── README.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsaz/csharp-snippet-productivity/HEAD/icon.png -------------------------------------------------------------------------------- /videos/comment.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsaz/csharp-snippet-productivity/HEAD/videos/comment.gif -------------------------------------------------------------------------------- /videos/addProject.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsaz/csharp-snippet-productivity/HEAD/videos/addProject.PNG -------------------------------------------------------------------------------- /videos/cliplistcom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsaz/csharp-snippet-productivity/HEAD/videos/cliplistcom.gif -------------------------------------------------------------------------------- /videos/clippropdic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsaz/csharp-snippet-productivity/HEAD/videos/clippropdic.gif -------------------------------------------------------------------------------- /media/addProjectIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsaz/csharp-snippet-productivity/HEAD/media/addProjectIcon.png -------------------------------------------------------------------------------- /videos/classInterface.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsaz/csharp-snippet-productivity/HEAD/videos/classInterface.PNG -------------------------------------------------------------------------------- /videos/createproject.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsaz/csharp-snippet-productivity/HEAD/videos/createproject.PNG -------------------------------------------------------------------------------- /videos/clipvarclassfunc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsaz/csharp-snippet-productivity/HEAD/videos/clipvarclassfunc.gif -------------------------------------------------------------------------------- /models/class6.mdl: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ${namespace}; 4 | 5 | public class ${fileName} 6 | { 7 | ${cursor} 8 | } 9 | -------------------------------------------------------------------------------- /models/interface6.mdl: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ${namespace}; 4 | 5 | public interface ${fileName} 6 | { 7 | ${cursor} 8 | } -------------------------------------------------------------------------------- /models/record6.mdl: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ${namespace}; 4 | 5 | public record ${fileName} 6 | { 7 | ${cursor} 8 | } 9 | -------------------------------------------------------------------------------- /models/struct6.mdl: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ${namespace}; 4 | 5 | public struct ${fileName} 6 | { 7 | ${cursor} 8 | } 9 | -------------------------------------------------------------------------------- /models/class.mdl: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ${namespace} 4 | { 5 | public class ${fileName} 6 | { 7 | ${cursor} 8 | } 9 | } -------------------------------------------------------------------------------- /models/record.mdl: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ${namespace} 4 | { 5 | public record ${fileName} 6 | { 7 | ${cursor} 8 | } 9 | } -------------------------------------------------------------------------------- /models/struct.mdl: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ${namespace} 4 | { 5 | public struct ${fileName} 6 | { 7 | ${cursor} 8 | } 9 | } -------------------------------------------------------------------------------- /models/interface.mdl: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ${namespace} 4 | { 5 | public interface ${fileName} 6 | { 7 | ${cursor} 8 | } 9 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .eslintrc.json 2 | .vscode/** 3 | .vscode-test/** 4 | out/test/** 5 | test/** 6 | src/** 7 | .gitignore 8 | tsconfig.json 9 | tslint.json 10 | vsc-extension-quickstart.md 11 | *.yml -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode/** 3 | .vscode-test/** 4 | .gitignore 5 | vsc-extension-quickstart.md 6 | out/test/** 7 | /out/ 8 | test/** 9 | *.vsix 10 | .yarnrc 11 | **/tsconfig.json 12 | **/.eslintrc.json 13 | **/*.map 14 | 15 | # Ignore lock files 16 | yarn.lock 17 | package-lock.json -------------------------------------------------------------------------------- /src/resource/createProjectWebView/GetNonce.ts: -------------------------------------------------------------------------------- 1 | export function getNonce() { 2 | let text = ''; 3 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 4 | for (let i = 0; i < 32; i++) { 5 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 6 | } 7 | return text; 8 | } -------------------------------------------------------------------------------- /media/reset.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-size: 13px; 4 | } 5 | 6 | *, 7 | *:before, 8 | *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | ol, 21 | ul { 22 | margin: 0; 23 | padding: 0; 24 | font-weight: normal; 25 | } 26 | 27 | img { 28 | max-width: 100%; 29 | height: auto; 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": ["es6"], 7 | "sourceMap": true, 8 | "rootDir": "src", 9 | "strict": true /* enable all strict type-checking options */, 10 | "strictNullChecks": true, 11 | "strictPropertyInitialization": false 12 | }, 13 | "exclude": ["node_modules", ".vscode-test", "src/_test*"] 14 | } 15 | -------------------------------------------------------------------------------- /.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 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { CommandRegister } from "./CommandRegister"; 3 | 4 | /** 5 | * This method is called when the extension is activated 6 | */ 7 | export function activate(context: vscode.ExtensionContext) { 8 | const commands = CommandRegister.getInstance(context); 9 | commands.initializeCommands(); 10 | } 11 | 12 | /** 13 | * This method is called when the extension is deactivated 14 | */ 15 | export function deactivate() {} 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.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 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/test/**/*.js" 30 | ], 31 | "preLaunchTask": "${defaultBuildTask}" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /media/sdks.txt: -------------------------------------------------------------------------------- 1 | 2.1.2 [C:\Program Files\dotnet\sdk] 2 | 2.1.4 [C:\Program Files\dotnet\sdk] 3 | 2.1.201 [C:\Program Files\dotnet\sdk] 4 | 2.1.202 [C:\Program Files\dotnet\sdk] 5 | 2.1.402 [C:\Program Files\dotnet\sdk] 6 | 2.1.403 [C:\Program Files\dotnet\sdk] 7 | 2.1.526 [C:\Program Files\dotnet\sdk] 8 | 2.1.617 [C:\Program Files\dotnet\sdk] 9 | 2.1.700 [C:\Program Files\dotnet\sdk] 10 | 2.1.701 [C:\Program Files\dotnet\sdk] 11 | 2.1.818 [C:\Program Files\dotnet\sdk] 12 | 2.2.301 [C:\Program Files\dotnet\sdk] 13 | 2.2.401 [C:\Program Files\dotnet\sdk] 14 | 2.2.402 [C:\Program Files\dotnet\sdk] 15 | 3.1.426 [C:\Program Files\dotnet\sdk] 16 | 5.0.101 [C:\Program Files\dotnet\sdk] 17 | 5.0.104 [C:\Program Files\dotnet\sdk] 18 | 5.0.203 [C:\Program Files\dotnet\sdk] 19 | 5.0.214 [C:\Program Files\dotnet\sdk] 20 | 5.0.303 [C:\Program Files\dotnet\sdk] 21 | 5.0.408 [C:\Program Files\dotnet\sdk] 22 | 6.0.101 [C:\Program Files\dotnet\sdk] 23 | 6.0.118 [C:\Program Files\dotnet\sdk] 24 | 6.0.202 [C:\Program Files\dotnet\sdk] 25 | 6.0.203 [C:\Program Files\dotnet\sdk] 26 | 6.0.321 [C:\Program Files\dotnet\sdk] 27 | 8.0.100 [C:\Program Files\dotnet\sdk] 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Richard Zampieri 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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "cSpell.ignoreWords": [ 12 | "backreference", 13 | "documentxml", 14 | "extention", 15 | "group", 16 | "json", 17 | "multiline", 18 | "snippets", 19 | "strikethrough", 20 | "to", 21 | "zampieri" 22 | ], 23 | "cSpell.words": [ 24 | "Blazor", 25 | "blazorserver", 26 | "blazorwasm", 27 | "classlib", 28 | "Creational", 29 | "designpattern", 30 | "forminline", 31 | "mauilib", 32 | "minwebapi", 33 | "mstest", 34 | "netcoreapp", 35 | "nunit", 36 | "paddding", 37 | "razorclasslib", 38 | "reactredux", 39 | "readlines", 40 | "richardzampieriprog", 41 | "submenu", 42 | "webapi", 43 | "xunit" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /media/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Add the Project Details

12 |
13 |
14 |

Select the project type

15 | 31 |
32 |
33 |

Select the project template

34 | 37 |
38 |
39 | 40 | 41 |
42 | 43 |
44 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/utils/sdk.provider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as fs from "fs"; 3 | 4 | export function getTargetFrameworks(sdksResource: vscode.Uri): string[] { 5 | // Cleaning the sdk's folder path 6 | let sdkFile: string = String(sdksResource.fsPath); 7 | sdkFile.replace("/", "\\"); 8 | sdkFile = sdkFile.substring(0, sdkFile.length); 9 | 10 | // clean file 11 | fs.truncate(sdksResource.fsPath, 0, () => {}); 12 | 13 | writeSDKOnFile(sdkFile); 14 | 15 | const sdksList: string = fs.readFileSync(sdksResource.fsPath, "utf8"); 16 | let lines: string[] = sdksList.split("\n"); 17 | let sdks: string[] = []; 18 | 19 | lines.forEach((line: string) => { 20 | let lineUpdated: string = line.replace(/\s+/g, ""); 21 | lineUpdated = lineUpdated.replace(/[^a-z0-9A-Z.]/g, ""); 22 | let sdk: string = lineUpdated.substring(0, 3); 23 | if (sdk) { 24 | sdks.push(sdk); 25 | } 26 | }); 27 | 28 | // Eliminate duplicates 29 | sdks = sdks.filter((value, index, self) => self.indexOf(value) === index); 30 | 31 | return sdks; 32 | } 33 | 34 | function writeSDKOnFile(sdkFile: string) { 35 | const os = process.platform; 36 | const terminal = getTerminal(); 37 | const terminalPath = vscode.workspace 38 | .getConfiguration("terminal.integrated") 39 | .get("shell.windows"); 40 | 41 | if (os === "win32") { 42 | if (terminalPath && (terminalPath as string).includes("cmd.exe")) { 43 | terminal.sendText(`dotnet --list-sdks > "${sdkFile}"`); 44 | } else if ( 45 | terminalPath && 46 | ((terminalPath as string).includes("bash.exe") || 47 | (terminalPath as string).includes("git-bash.exe")) 48 | ) { 49 | terminal.sendText(`dotnet --list-sdks > "${sdkFile}"`); 50 | } else { 51 | // Default to PowerShell command 52 | terminal.sendText(`Write-Output --noEnumeration | dotnet --list-sdks > "${sdkFile}"`); 53 | } 54 | } else { 55 | terminal.sendText(`echo -n | dotnet --list-sdks > "${sdkFile}"`); 56 | } 57 | terminal.sendText("clear"); 58 | } 59 | 60 | function getTerminal(): vscode.Terminal { 61 | return vscode.window.activeTerminal === undefined 62 | ? vscode.window.createTerminal() 63 | : vscode.window.activeTerminal; 64 | } 65 | -------------------------------------------------------------------------------- /src/CommandRegister.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { CreateProjectPanel } from "./resource/createProjectWebView/CreateProject"; 3 | import { SmartComments } from "./resource/smartComments/SmartComments"; 4 | import { ContextualMenu } from "./resource/contextualMenu/ContextualMenu"; 5 | import { AddProjectToSolution } from "./resource/addProjectToSolution/AddProjectToSolution"; 6 | 7 | /** 8 | * Singleton class to hold all CommandRegister configuration. 9 | * This class is used to register new commands available in the system. 10 | */ 11 | export class CommandRegister { 12 | private static _instance: CommandRegister | null = null; 13 | private readonly context: vscode.ExtensionContext; 14 | private framework: string; 15 | 16 | private constructor(context: vscode.ExtensionContext) { 17 | this.context = context; 18 | this.framework = context.globalState.get("framework") ?? ""; 19 | } 20 | 21 | public static getInstance(context: vscode.ExtensionContext): CommandRegister { 22 | if (!CommandRegister._instance) { 23 | CommandRegister._instance = new CommandRegister(context); 24 | } 25 | return CommandRegister._instance; 26 | } 27 | 28 | /** 29 | * Initialize all commands 30 | */ 31 | public initializeCommands(): void { 32 | this.registerCommand("csharp-snippet-productivity.createProject", async () => { 33 | CreateProjectPanel.createOrShow(this.context); 34 | }); 35 | 36 | this.registerCommand( 37 | "csharp-snippet-productivity.addProjectToSolution", 38 | async (uri: vscode.Uri) => { 39 | AddProjectToSolution.init(uri, this.context); 40 | } 41 | ); 42 | 43 | // Initialize smart comments 44 | this.activateSmartComments(); 45 | 46 | // Initialize contextual menu 47 | this.activateContextualMenu(); 48 | } 49 | 50 | /** 51 | * Activate SmartComments 52 | */ 53 | private activateSmartComments(): void { 54 | const smartComment = new SmartComments(this.context); 55 | smartComment.activateSmartComments(); 56 | } 57 | 58 | /** 59 | * Activate Contextual Menu 60 | */ 61 | private activateContextualMenu(): void { 62 | const types = ["class", "interface", "struct", "record"]; 63 | for (const type of types) { 64 | this.registerCommand( 65 | `csharp-snippet-productivity.create${type.charAt(0).toUpperCase() + type.slice(1)}`, 66 | async (uri: vscode.Uri) => { 67 | ContextualMenu.init(uri, type, this.framework); 68 | } 69 | ); 70 | } 71 | } 72 | 73 | /** 74 | * Utility function to register a command 75 | * @param commandId - The ID of the command 76 | * @param callback - The callback function to execute when the command is triggered 77 | */ 78 | private registerCommand(commandId: string, callback: (...args: any[]) => any): void { 79 | this.context.subscriptions.push(vscode.commands.registerCommand(commandId, callback)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/resource/smartComments/SmartComments.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Parser } from "./Parser"; 3 | 4 | export class SmartComments { 5 | private context: vscode.ExtensionContext; 6 | 7 | constructor(context: vscode.ExtensionContext) { 8 | this.context = context; 9 | } 10 | 11 | public activateSmartComments() { 12 | let activeEditor: vscode.TextEditor; 13 | let parser: Parser = new Parser(); 14 | 15 | // Called to handle events below 16 | let updateDecorations = function (useHash = false) { 17 | // if no active window is open, return 18 | if (!activeEditor) { 19 | return; 20 | } 21 | 22 | // check language support 23 | if (!parser.supportedLanguage) { 24 | return; 25 | } 26 | 27 | // Finds the single line comments using the language comment delimiter 28 | parser.FindSingleLineComments(activeEditor); 29 | 30 | // Finds the multi line comments using the language comment delimiter 31 | parser.FindBlockComments(activeEditor); 32 | 33 | // Finds the jsdoc comments 34 | parser.FindJSDocComments(activeEditor); 35 | 36 | // Apply the styles set in the package.json 37 | parser.ApplyDecorations(activeEditor); 38 | }; 39 | 40 | // Get the active editor for the first time and initialize the regex 41 | if (vscode.window.activeTextEditor) { 42 | activeEditor = vscode.window.activeTextEditor; 43 | 44 | // Set the regex patterns for the specified language's comments 45 | parser.SetRegex(activeEditor.document.languageId); 46 | 47 | // Trigger first update of decorators 48 | triggerUpdateDecorations(); 49 | } 50 | 51 | // * Handle active file changed 52 | vscode.window.onDidChangeActiveTextEditor( 53 | (editor) => { 54 | if (editor) { 55 | activeEditor = editor; 56 | 57 | // Set regex for updated language 58 | parser.SetRegex(editor.document.languageId); 59 | 60 | // Trigger update to set decorations for newly active file 61 | triggerUpdateDecorations(); 62 | } 63 | }, 64 | null, 65 | this.context.subscriptions 66 | ); 67 | 68 | // * Handle file contents changed 69 | vscode.workspace.onDidChangeTextDocument( 70 | (event) => { 71 | // Trigger updates if the text was changed in the same document 72 | if (activeEditor && event.document === activeEditor.document) { 73 | triggerUpdateDecorations(); 74 | } 75 | }, 76 | null, 77 | this.context.subscriptions 78 | ); 79 | 80 | // * IMPORTANT: 81 | // To avoid calling update too often, 82 | // set a timer for 200ms to wait before updating decorations 83 | var timeout: NodeJS.Timeout; 84 | function triggerUpdateDecorations() { 85 | if (timeout) { 86 | clearTimeout(timeout); 87 | } 88 | timeout = setTimeout(updateDecorations, 200); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/resource/addProjectToSolution/AddProjectToSolution.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as path from "path"; 3 | import * as fs from "fs"; 4 | import { Panel } from "./Panel"; 5 | 6 | export class AddProjectToSolution { 7 | public static init(uri: vscode.Uri, context: vscode.ExtensionContext) { 8 | vscode.window 9 | .showInputBox({ 10 | ignoreFocusOut: true, 11 | prompt: "Type the project name", 12 | value: "New project name", 13 | }) 14 | .then((newFileName) => { 15 | if (typeof newFileName === undefined || newFileName === "") { 16 | vscode.window.showErrorMessage( 17 | "Please input a valid name or press Scape to cancel the operation!" 18 | ); 19 | return this.init(uri, context); 20 | } 21 | 22 | // Acquiring the solution root folder 23 | let root = vscode.workspace.workspaceFolders 24 | ?.map((folder) => folder.uri.path)[0] 25 | .replace(/\//g, "\\"); 26 | root = root?.slice(1, root.length); 27 | 28 | // Removing white spaces within the new project name 29 | if (newFileName) { 30 | newFileName = newFileName.replace(/\s/g, ""); 31 | } 32 | 33 | // Setting the new project path 34 | const newFilePath = root + path.sep + newFileName; 35 | 36 | // Verify if project already exist 37 | if (fs.existsSync(newFilePath)) { 38 | vscode.window.showErrorMessage(`Project ${newFileName} already exist`); 39 | return this.init(uri, context); 40 | } 41 | 42 | let rootDir = getProjectRootDirOrFilePath(root); 43 | 44 | if (rootDir === null) { 45 | vscode.window.showErrorMessage("Unable to find *.sln (solution)"); 46 | return; 47 | } 48 | 49 | // Create a project 50 | if (newFileName) { 51 | const panel = new Panel(context, newFileName, rootDir, "Add Project", { 52 | folder: "media", 53 | file: "addProjectIcon.png", 54 | }); 55 | panel.webViewPanel?.onDidDispose(() => { 56 | panel.webViewPanel = undefined; 57 | }, context.subscriptions); 58 | } 59 | }); 60 | } 61 | } 62 | 63 | // function to detect the root directory where the .csproj is included 64 | function getProjectRootDirOrFilePath(filePath: any) { 65 | const paths: string[] = filePath.split("\\"); 66 | const solution: string = filePath + path.sep + paths[paths.length - 1] + ".sln"; 67 | 68 | if (!fs.existsSync(solution)) { 69 | return null; 70 | } 71 | return solution; 72 | } 73 | -------------------------------------------------------------------------------- /media/vscode.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --container-paddding: 20px; 3 | --input-padding-vertical: 6px; 4 | --input-padding-horizontal: 4px; 5 | --input-margin-vertical: 4px; 6 | --input-margin-horizontal: 0; 7 | } 8 | 9 | body { 10 | padding: 0 var(--container-paddding); 11 | color: var(--vscode-foreground); 12 | font-size: var(--vscode-font-size); 13 | font-weight: var(--vscode-font-weight); 14 | font-family: var(--vscode-font-family); 15 | background-color: var(--vscode-editor-background); 16 | } 17 | 18 | ol, 19 | ul { 20 | padding-left: var(--container-paddding); 21 | } 22 | 23 | body > *, 24 | form > * { 25 | margin-block-start: var(--input-margin-vertical); 26 | margin-block-end: var(--input-margin-vertical); 27 | } 28 | 29 | *:focus { 30 | outline-color: var(--vscode-focusBorder) !important; 31 | } 32 | 33 | a { 34 | color: var(--vscode-textLink-foreground); 35 | } 36 | 37 | a:hover, 38 | a:active { 39 | color: var(--vscode-textLink-activeForeground); 40 | } 41 | 42 | code { 43 | font-size: var(--vscode-editor-font-size); 44 | font-family: var(--vscode-editor-font-family); 45 | } 46 | 47 | button { 48 | border: none; 49 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 50 | width: 100%; 51 | text-align: center; 52 | outline: 1px solid transparent; 53 | outline-offset: 2px !important; 54 | color: var(--vscode-button-foreground); 55 | background: var(--vscode-button-background); 56 | } 57 | 58 | button:hover { 59 | cursor: pointer; 60 | background: var(--vscode-button-hoverBackground); 61 | } 62 | 63 | button:focus { 64 | outline-color: var(--vscode-focusBorder); 65 | } 66 | 67 | button.secondary { 68 | color: var(--vscode-button-secondaryForeground); 69 | background: var(--vscode-button-secondaryBackground); 70 | } 71 | 72 | button.secondary:hover { 73 | background: var(--vscode-button-secondaryHoverBackground); 74 | } 75 | 76 | input:not([type="checkbox"]), 77 | textarea { 78 | display: block; 79 | width: 100%; 80 | border: none; 81 | font-family: var(--vscode-font-family); 82 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 83 | color: var(--vscode-input-foreground); 84 | outline-color: var(--vscode-input-border); 85 | background-color: var(--vscode-input-background); 86 | } 87 | 88 | input::placeholder, 89 | textarea::placeholder { 90 | color: var(--vscode-input-placeholderForeground); 91 | } 92 | 93 | /* custom select */ 94 | #custom-select, 95 | #project-group-select { 96 | width: 230px; 97 | height: 30px; 98 | } 99 | 100 | #custom-select2 { 101 | width: 230px; 102 | height: 30px; 103 | } 104 | 105 | #create-project-button { 106 | width: 210px; 107 | height: 30px; 108 | } 109 | 110 | #inputLocal { 111 | width: 500px; 112 | height: 30px; 113 | background-color: #434443; 114 | } 115 | 116 | #selectFolder { 117 | width: 30px; 118 | height: 30px; 119 | } 120 | 121 | #projectName { 122 | width: 500px; 123 | height: 30px; 124 | background-color: #434443; 125 | color: white; 126 | } 127 | 128 | #solution { 129 | width: 500px; 130 | height: 30px; 131 | background-color: #434443; 132 | color: white; 133 | } 134 | 135 | #forminline { 136 | display: flex; 137 | flex-flow: row wrap; 138 | } 139 | -------------------------------------------------------------------------------- /src/resource/todoList/icon.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 38 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /snippets/designpattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "-> Creational::Singleton": { 3 | "prefix": "singleton", 4 | "body": [ 5 | "class Singleton {", 6 | "\tprivate static readonly Lazy _instance = new Lazy(() => new Singleton());", 7 | "\tpublic static Singleton Instance => _instance.Value;", 8 | "\tprivate Singleton() { }", 9 | "}" 10 | ], 11 | "description": "Creational design pattern singleton" 12 | }, 13 | "-> Creational::Factory Method": { 14 | "prefix": "factoryMethod", 15 | "body": [ 16 | "public abstract class Creator {", 17 | "\tpublic abstract IProduct FactoryMethod();", 18 | "\tpublic string SomeOperation() {", 19 | "\t\tvar product = FactoryMethod();", 20 | "\t\treturn $\"Creator: The same creator's code has just worked with {product.Operation()}\";", 21 | "\t}", 22 | "}", 23 | "", 24 | "public class ConcreteCreator1 : Creator {", 25 | "\tpublic override IProduct FactoryMethod() => new ConcreteProduct1();", 26 | "}", 27 | "", 28 | "public class ConcreteCreator2 : Creator {", 29 | "\tpublic override IProduct FactoryMethod() => new ConcreteProduct2();", 30 | "}", 31 | "", 32 | "public interface IProduct {", 33 | "\tstring Operation();", 34 | "}", 35 | "", 36 | "public class ConcreteProduct1 : IProduct {", 37 | "\tpublic string Operation() => \"Result of ConcreteProduct1\";", 38 | "}", 39 | "", 40 | "public class ConcreteProduct2 : IProduct {", 41 | "\tpublic string Operation() => \"Result of ConcreteProduct2\";", 42 | "}" 43 | ], 44 | "description": "Creational design pattern factory method" 45 | }, 46 | "-> Structural::Adapter": { 47 | "prefix": "adapter", 48 | "body": [ 49 | "public interface ITarget {", 50 | "\tstring GetRequest();", 51 | "}", 52 | "", 53 | "public class Adaptee {", 54 | "\tpublic string GetSpecificRequest() {", 55 | "\t\treturn 'Specific Request.';", 56 | "\t}", 57 | "}", 58 | "", 59 | "public class Adapter : ITarget {", 60 | "\tprivate readonly Adaptee _adaptee;", 61 | "\tpublic Adapter(Adaptee adaptee) {", 62 | "\t\tthis._adaptee = adaptee;", 63 | "\t}", 64 | "", 65 | "\tpublic string GetRequest() {", 66 | "\t\treturn $'This is {this._adaptee.GetSpecificRequest()}';", 67 | "\t}", 68 | "}" 69 | ], 70 | "description": "Structural design pattern adapter" 71 | }, 72 | "-> Behavioral::Observer": { 73 | "prefix": "observer", 74 | "body": [ 75 | "public interface IObserver {", 76 | "\tvoid Update(Subject subject);", 77 | "}", 78 | "", 79 | "public interface ISubject {", 80 | "\tevent Action OnChange;", 81 | "\tvoid Notify();", 82 | "}", 83 | "", 84 | "public class Subject : ISubject {", 85 | "\tpublic int State { get; private set; } = -1;", 86 | "\tpublic event Action? OnChange;", 87 | "", 88 | "\tpublic void Notify() {", 89 | "\t\tConsole.WriteLine(\"Subject: Notifying observers...\");", 90 | "\t\tOnChange?.Invoke(this);", 91 | "\t}", 92 | "", 93 | "\tpublic void SomeBusinessLogic() {", 94 | "\t\tConsole.WriteLine(\"Subject: I'm doing something important.\");", 95 | "\t\tState = new Random().Next(0, 10);", 96 | "\t\tThread.Sleep(15);", 97 | "\t\tConsole.WriteLine($\"Subject: My state has just changed to: {State}\");", 98 | "\t\tNotify();", 99 | "\t}", 100 | "}", 101 | "", 102 | "public class ConcreteObserverA : IObserver {", 103 | "\tpublic void Update(Subject subject) {", 104 | "\t\tif (subject.State < 3) {", 105 | "\t\t\tConsole.WriteLine(\"ConcreteObserverA: Reacted to the event.\");", 106 | "\t\t}", 107 | "\t}", 108 | "}", 109 | "", 110 | "public class ConcreteObserverB : IObserver {", 111 | "\tpublic void Update(Subject subject) {", 112 | "\t\tif (subject.State == 0 || subject.State >= 2) {", 113 | "\t\t\tConsole.WriteLine(\"ConcreteObserverB: Reacted to the event.\");", 114 | "\t\t}", 115 | "\t}", 116 | "}" 117 | ], 118 | "description": "Behavioral design pattern observer" 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "csharp-snippet-productivity" extension will be documented in this file. 4 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 5 | 6 | ## [Released] 7 | 8 | ## [2.1.1] - [2024-01-18] 9 | 10 | ## What's new in 2.1.1 11 | 12 | > - **_New Feature added_**: Added all current scaffold commands from context menu available in the command palette. 13 | > - **_New Feature added_**: Added template validation against the .NET SDK installed on the machine. 14 | > - **_Fix_**: Adjusted the AddProject command to work with the new template validation and project group selection. 15 | 16 | Observations: 17 | 18 | The commands available in the context menu follow a different workflow than the commands available in the command palette. The commands in the context menu will create the project or resource in the same clicked folder. 19 | 20 | The commands in the command palette will ask the user to select the project, create or select the folder, and then create the project. 21 | 22 | Expect a different interaction when using the commands in the context menu and the command palette. 23 | 24 | All commands are available via shortcut keys. You can find the shortcut keys in the command palette. 25 | 26 | - `Ctrl + alt + /` + `p` - Create Project 27 | - `Ctrl + alt + /` + `c` - Create Class 28 | - `Ctrl + alt + /` + `i` - Create Interface 29 | - `Ctrl + alt + /` + `r` - Create Record 30 | - `Ctrl + alt + /` + `s` - Create Struct 31 | - `Ctrl + alt + /` + `a` - Add Project to Solution 32 | 33 | ## [2.0.1] - [2024-01-14] 34 | 35 | ## What's new in 2.0.1 36 | 37 | > - **_Fix_**: Fixed issues related to design patterns snippets. Added a more modern code approach to the snippets. 38 | 39 | ## [2.0.0] - [2024-01-14] 40 | 41 | ## What's new in 2.0.0 42 | 43 | > - **_New Feature added_**: Added support for all project types and templates under project creation. 44 | > - **_New Feature added_**: Support for .NET 7.0 and .NET 8.0 45 | > - **_Performance improvements_**: Extension loading time decreased and command execution time decreased. 46 | > - **_Fix_**: Fixed snippet conflicts and non standard snippets. 47 | > - **_Enhancement_**: Validates the project template and framework compatibility based on the .NET SDK installed on the machine. 48 | > - **_Enhancement_**: Added validation to avoid creating projects with empty spaces. 49 | > - **_Enhancement_**: Reinforce the use of the default folder for project creation. 50 | 51 | ## [1.3.0] - [2022-07-03] 52 | 53 | > - **_New Feature added_**: Minimal Web API, MStest, xUnit, NUnit project template added. 54 | > - **_Fix_**: Creating Solution with the same name in the same directory. 55 | > - **_Fix_**: find-parent-dir dependency updated to remove the error message from vscode. 56 | 57 | ## [1.2.9] - [2022-05-14] 58 | 59 | > - **_New Feature added_**: Scoped namespaces in the .NET 6.0 60 | > - **_Improvement_**: Project creation highlighting the `create project button` after the project name is typed and tab is pressed. 61 | 62 | ## [1.2.8] - [2021-11-13] 63 | 64 | > - **_New Feature added_**: Project support for C# .NET Core 6.0 65 | 66 | ## [1.2.7] - [2021-09-04] 67 | 68 | - **_Fix_**: Classes, Interfaces, and other types created correctly even when the user type incorrect names. 69 | - **_New Features added_**: Added a default folder for project creation. Add this configuration to your settings with your path: `"csharp-snippet-productivity.defaultFolderForProjectCreation": "D:\\"` **{Your path}** 70 | 71 | ## [1.2.6] - 2021-08-28 72 | 73 | - **_Fix_**: Creating solutions in folders path with spaces were not possible. Now solutions and projects can be created in folders with spaces. **i.e: `c:\Your Project Folder\Solution.sln`** 74 | 75 | ## [1.2.5] - 2021-08-01 76 | 77 | - **_Fix_**: Removed the notes feature preview accidentally uploaded 78 | 79 | ## [1.2.4] - 2021-08-01 80 | 81 | - **_Fix_**: Solution was being created with project name rather than solution data from solution field. 82 | - **_New Features added_**: 83 | - **_Add Project to a Solution_** : Capability to add projects to the same solution with a click of a button. You can select a different project framework as well as the template. 84 | - **_Submenu With Options_** : 85 | - Create Class 86 | - Create Interface 87 | - Create Record 88 | - Create Struct 89 | 90 | ## [1.2.3] - 2021-07-18 91 | 92 | - **_Fix_**: .NET target frameworks list on project creation are based on OS and SDKs installed. 93 | - **_Enhancement_**: Design patterns snippets added. It will create a commented pattern code to be used as reference 94 | - **_singleton_** : Creational singleton pattern 95 | - **_factoryMethod_** : Creational factory method pattern 96 | - **_adapter_** : Structural adapter pattern 97 | - **_observer_**: Structural observer pattern 98 | - **_Enhancement_**: Regex snippet cheat sheet added. 99 | - **_regex_** : Regex cheat sheet 100 | 101 | ## [1.2.2] - 2021-03-24 102 | 103 | - Enhancement: When creating classes or interfaces system will consider if you have a \YourUniqueNamespace\ tag. If the tag is not found system will use your project name as your root namespace. 104 | 105 | ## [1.2.1] - 2021-02-28 106 | 107 | - Fixing command not found issue on 1.2 version 108 | 109 | ## [1.2.0] - 2021-02-28 110 | 111 | - Added command to create Class from the context/menu 112 | - Added command to create Interface from the context/menu 113 | 114 | ## [1.1.0] - 2021-02-23 115 | 116 | - Command to create projects 117 | - Projects templates supported: 118 | - Blazor Server App 119 | - Blazor WebAssembly App 120 | - Console Application 121 | - Class Library 122 | - .NET Core: Empty, MVC, Razor Page, Angular SPA, React SPA, React/Redux SPA, Web Api, GRPC Services, Razor Class Library 123 | - Added snippets for creating arrays, lists and dictionaries using var 124 | - var myArray = new type[size]; 125 | - var myList = new List\(); 126 | - var myDictionary = new Dictionary\(); 127 | 128 | ## [1.0.0] - 2021-02-11 129 | 130 | - Initial project release 131 | - Custom comments: colorful comments for better coding organization 132 | - NORMAL comments [***//***] 133 | - TODO: comments [***todo***] 134 | - REVIEW: comments [***review***] 135 | - BUG: comments [***bug***] 136 | - RESEARCH: comments [***research***] 137 | - General C# snippets 138 | - XML Documentation snippets 139 | -------------------------------------------------------------------------------- /src/utils/terminal-cmd.provider.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | import * as vscode from "vscode"; 3 | 4 | export type Message = { 5 | command: string; 6 | filepath: string; 7 | solution: string; 8 | project: string; 9 | template: string; 10 | framework: string; 11 | }; 12 | 13 | export type TemplateCompatibility = { 14 | [key: string]: string[]; 15 | }; 16 | 17 | export const TEMPLATE_COMPATIBILITY: TemplateCompatibility = { 18 | react: ["net5.0", "net6.0", "net7.0"], 19 | maui: ["net7.0", "net8.0"], 20 | "maui-blazor": ["net7.0", "net8.0"], 21 | mauilib: ["net7.0", "net8.0"], 22 | "maui-page-csharp": ["net7.0", "net8.0"], 23 | "maui-page-xaml": ["net7.0", "net8.0"], 24 | "maui-view-csharp": ["net7.0", "net8.0"], 25 | "maui-view-xaml": ["net7.0", "net8.0"], 26 | "maui-dict-xaml": ["net7.0", "net8.0"], 27 | android: ["net6.0", "net7.0", "net8.0"], 28 | androidlib: ["net6.0", "net7.0", "net8.0"], 29 | webapi: ["netcoreapp3.1", "net5.0", "net6.0", "net7.0", "net8.0"], 30 | webapiaot: ["net8.0"], 31 | apicontroller: ["net5.0", "net6.0", "net7.0", "net8.0"], 32 | blazorserver: ["netcoreapp3.1", "net5.0", "net6.0", "net7.0"], 33 | "blazorserver-empty": ["net7.0"], 34 | blazor: ["net5.0", "net6.0", "net7.0", "net8.0"], 35 | "blazorwasm-empty": ["net5.0", "net6.0", "net7.0", "net8.0"], 36 | blazorwasm: ["net5.0", "net6.0", "net7.0", "net8.0"], 37 | classlib: ["netstandard2.0", "netstandard2.1", "net5.0", "net6.0", "net7.0", "net8.0"], 38 | console: ["net5.0", "net6.0", "net7.0", "net8.0"], 39 | grpc: ["net5.0", "net6.0", "net7.0", "net8.0"], 40 | mvc: ["net5.0", "net6.0", "net7.0", "net8.0"], 41 | webapp: ["net5.0", "net6.0", "net7.0", "net8.0"], 42 | angular: ["net5.0", "net6.0", "net7.0", "net8.0"], 43 | ios: ["net6.0", "net7.0", "net8.0"], 44 | ioslib: ["net6.0", "net7.0", "net8.0"], 45 | mstest: ["net5.0", "net6.0", "net7.0", "net8.0"], 46 | "mstest-playwright": ["net6.0", "net7.0", "net8.0"], 47 | nunit: ["net5.0", "net6.0", "net7.0", "net8.0"], 48 | "nunit-playwright": ["net6.0", "net7.0", "net8.0"], 49 | xunit: ["net5.0", "net6.0", "net7.0", "net8.0"], 50 | winforms: ["net5.0", "net6.0", "net7.0", "net8.0"], 51 | wpf: ["net5.0", "net6.0", "net7.0", "net8.0"], 52 | // ... Add more templates as needed 53 | }; 54 | 55 | export class Command { 56 | protected terminal: vscode.Terminal; 57 | protected message: Message; 58 | 59 | constructor(terminal: vscode.Terminal, message: Message) { 60 | this.terminal = terminal; 61 | this.message = message; 62 | } 63 | 64 | executeCommonCommands() { 65 | this.terminal.sendText(`mkdir '${this.message.filepath}\\${this.message.solution}'`); 66 | this.terminal.sendText( 67 | `dotnet new sln -n ${this.message.solution} -o '${this.message.filepath}\\${this.message.solution}' --force` 68 | ); 69 | this.terminal.sendText( 70 | `mkdir '${this.message.filepath}\\${this.message.solution}\\${this.message.project}'` 71 | ); 72 | } 73 | 74 | addProjectToSolution() { 75 | this.terminal.sendText( 76 | `dotnet sln '${this.message.filepath}\\${this.message.solution}\\${this.message.solution}.sln' add '${this.message.filepath}\\${this.message.solution}\\${this.message.project}\\${this.message.project}.csproj'` 77 | ); 78 | } 79 | 80 | openInVsCode() { 81 | this.terminal.sendText(`code '${this.message.filepath}\\${this.message.solution}' -r`); 82 | } 83 | 84 | execute() { 85 | // This method will be overridden in derived classes 86 | } 87 | 88 | isFrameworkCompatible() { 89 | // Verify if template is not undefined 90 | if (!this.message.template || !(this.message.template in TEMPLATE_COMPATIBILITY)) { 91 | return true; 92 | } 93 | return TEMPLATE_COMPATIBILITY[this.message.template].includes(this.message.framework); 94 | } 95 | } 96 | 97 | export class GrpcCommand extends Command { 98 | execute(): void { 99 | this.executeCommonCommands(); 100 | this.terminal.sendText( 101 | `dotnet new grpc -n ${this.message.project} -o '${this.message.filepath}\\${this.message.solution}\\${this.message.project}' --force` 102 | ); 103 | this.addProjectToSolution(); 104 | this.openInVsCode(); 105 | } 106 | } 107 | 108 | export class MinWebApiCommand extends Command { 109 | execute() { 110 | if (this.message.framework !== "net6.0") { 111 | vscode.window.showWarningMessage("Please select net6.0 for Minimal WebAPI"); 112 | return; 113 | } 114 | this.executeCommonCommands(); 115 | this.terminal.sendText( 116 | `dotnet new webapi -minimal --language c# -n ${this.message.project} -o '${this.message.filepath}\\${this.message.solution}\\${this.message.project}' --framework ${this.message.framework} --force` 117 | ); 118 | this.addProjectToSolution(); 119 | this.openInVsCode(); 120 | } 121 | } 122 | 123 | export class DefaultCommand extends Command { 124 | execute() { 125 | if (!this.isFrameworkCompatible()) { 126 | // Format list of compatible frameworks 127 | const compatibleFrameworks = TEMPLATE_COMPATIBILITY[this.message.template] 128 | .map((f) => `'${f.substring(3)}'`) 129 | .join(", "); 130 | 131 | vscode.window.showWarningMessage( 132 | `Please select a compatible framework for ${this.message.template} - [${ 133 | compatibleFrameworks || "None" 134 | }]` 135 | ); 136 | return; 137 | } 138 | 139 | this.executeCommonCommands(); 140 | this.terminal.sendText( 141 | `dotnet new ${this.message.template} -n ${this.message.project} -o '${this.message.filepath}\\${this.message.solution}\\${this.message.project}' --framework ${this.message.framework} --force` 142 | ); 143 | this.addProjectToSolution(); 144 | this.openInVsCode(); 145 | } 146 | } 147 | 148 | export class CommandFactory { 149 | static getCommand(terminal: vscode.Terminal, message: Message) { 150 | switch (message.template) { 151 | case "grpc": 152 | return new GrpcCommand(terminal, message); 153 | case "minwebapi": 154 | return new MinWebApiCommand(terminal, message); 155 | // Cases for other templates... 156 | default: 157 | return new DefaultCommand(terminal, message); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /snippets/documentxml.json: -------------------------------------------------------------------------------- 1 | { 2 | "-> XML Summary": { 3 | "prefix": "xml-summary", 4 | "body": [ 5 | "/// ", 6 | "/// ${1:The main class}", 7 | "/// ${2:Contain all methods for performing basic operations}", 8 | "/// " 9 | ], 10 | "description": "The tag adds brief information about a type or member" 11 | }, 12 | "-> XML Remarks": { 13 | "prefix": "xml-remarks", 14 | "body": [ 15 | "/// ", 16 | "/// ${1:The main class}", 17 | "/// ${2:Contain all methods for performing basic operations}", 18 | "/// ", 19 | "/// ", 20 | "/// ${3:This class can add, subtract, multiply and divide}", 21 | "/// " 22 | ], 23 | "description": "The tag supplements the information about types or members that the tag provides" 24 | }, 25 | "-> XML Returns": { 26 | "prefix": "xml-returns", 27 | "body": [ 28 | "/// ", 29 | "/// ${1:Add two integers and return the result}", 30 | "/// ", 31 | "/// ", 32 | "/// ${2:The sum of two integers}", 33 | "/// " 34 | ], 35 | "description": "The tag describes the return value of a method declaration" 36 | }, 37 | "-> XML Value": { 38 | "prefix": "xml-value", 39 | "body": [ 40 | "/// ${1:text}" 41 | ], 42 | "description": "The tag is similar to the tag, except that you use it for properties" 43 | }, 44 | "-> XML Example": { 45 | "prefix": "xml-example", 46 | "body": [ 47 | "/// ", 48 | "/// ${1:Add two integers and return the result}", 49 | "/// ", 50 | "/// ", 51 | "/// ${2:The sum of two integers}", 52 | "/// ", 53 | "/// ", 54 | "/// ", 55 | "/// int a = 5, b = 10;", 56 | "/// int sum = a + b;", 57 | "/// Console.WriteLine(\"Sum :\"sum)", 58 | "/// ", 59 | "/// " 60 | ], 61 | "description": "You use the tag to include an example in your XML documentation. This involves using the child tag" 62 | }, 63 | "-> XML Para": { 64 | "prefix": "xml-para", 65 | "body": [ 66 | "/// ${1:text}" 67 | ], 68 | "description": "You use the tag to format the content within its parent tag. is usually used inside a tag, such as or , to divide text into paragraphs. You can format the contents of the tag for your class definition" 69 | }, 70 | "-> XML C": { 71 | "prefix": "xml-c", 72 | "body": [ 73 | "/// ${1:text}" 74 | ], 75 | "description": "Still on the topic of formatting, you use the tag for marking part of text as code. It's like the tag but inline. It's useful when you want to show a quick code example as part of a tag's content" 76 | }, 77 | "-> XML Exception": { 78 | "prefix": "xml-exception", 79 | "body": [ 80 | "/// ${2:Param is max and the other greater than 0}" 81 | ], 82 | "description": "by using the tag, you let your developers know that a method can throw specific exceptions" 83 | }, 84 | "-> XML See": { 85 | "prefix": "xml-see", 86 | "body": [ 87 | "/// " 88 | ], 89 | "description": "the tag lets you create a clickable link to a documentation page for another code element" 90 | }, 91 | "-> XML Seealso": { 92 | "prefix": "xml-seealso", 93 | "body": [ 94 | "/// " 95 | ], 96 | "description": "you use the tag in the same way you do the tag. The only difference is that its content is typically placed in a \"See Also\" section" 97 | }, 98 | "-> XML Param": { 99 | "prefix": "xml-param", 100 | "body": [ 101 | "/// ${2:description}" 102 | ], 103 | "description": "you use the tag to describe a method's parameters" 104 | }, 105 | "XML Typeparam": { 106 | "prefix": "xml-typeparam", 107 | "body": [ 108 | "/// ${2:description}" 109 | ], 110 | "description": "You use tag just like the tag but for generic type or method declarations to describe a generic parameter" 111 | }, 112 | "XML Paramref": { 113 | "prefix": "xml-paramref", 114 | "body": [ 115 | "/// " 116 | ], 117 | "description": "sometimes you might be in the middle of describing what a method does in what could be a tag, and you might want to make a reference to a parameter" 118 | }, 119 | "XML Typeparamref": { 120 | "prefix": "xml-typeparamref", 121 | "body": [ 122 | "/// " 123 | ], 124 | "description": "you use tag just like the tag but for generic type or method declarations to describe a generic parameter" 125 | }, 126 | "-> XML List": { 127 | "prefix": "xml-list", 128 | "body": [ 129 | "/// ", 130 | "/// ", 131 | "/// ${1:term}", 132 | "/// ${2:description}", 133 | "/// ", 134 | "/// ", 135 | "/// ${3:term}", 136 | "/// ${4:description}", 137 | "/// ", 138 | "/// " 139 | ], 140 | "description": "you use the tag to format documentation information as an ordered list, unordered list, or table" 141 | }, 142 | "-> XML Inheritdoc": { 143 | "prefix": "xml-inheritdoc", 144 | "body": [ 145 | "/// " 146 | ], 147 | "description": "you can use the tag to inherit XML comments from base classes, interfaces, and similar methods" 148 | }, 149 | "-> XML Include": { 150 | "prefix": "xml-include", 151 | "body": [ 152 | "/// " 153 | ], 154 | "description": "the tag lets you refer to comments in a separate XML file that describe the types and members in your source code, as opposed to placing documentation comments directly in your source code file" 155 | } 156 | } -------------------------------------------------------------------------------- /media/addProject.js: -------------------------------------------------------------------------------- 1 | // This script will be run within the webview itself 2 | // It cannot access the main VS Code APIs directly. 3 | 4 | (function () { 5 | // vscode api 6 | const vscode = acquireVsCodeApi(); 7 | 8 | // Html elements bindings 9 | const buttonCreateProject = document.getElementById("create-project-button"); 10 | const template = document.getElementById("custom-select"); 11 | const project = document.getElementById("projectName"); 12 | const framework = document.getElementById("custom-select2"); 13 | const projectGroupSelect = document.getElementById("project-group-select"); 14 | 15 | document.addEventListener("DOMContentLoaded", function (event) { 16 | populateTemplateSelect(projectGroupSelect.value); 17 | buttonCreateProject.disabled = "true"; 18 | buttonCreateProject.style.backgroundColor = "#3C3C3C"; 19 | fieldValidation(); 20 | }); 21 | 22 | /* Project group select */ 23 | const projectGroupToTemplates = { 24 | api: [ 25 | { templateName: ".NET Core Web API", shortName: "webapi" }, 26 | { templateName: ".NET Core Web API (native AOT)", shortName: "webapiaot" }, 27 | { templateName: "API Controller", shortName: "apicontroller" }, 28 | ], 29 | blazor: [ 30 | { templateName: ".NET MAUI Blazor Hybrid App", shortName: "maui-blazor" }, 31 | { templateName: "Blazor Server App", shortName: "blazorserver" }, 32 | { templateName: "Blazor Server App Empty", shortName: "blazorserver-empty" }, 33 | { templateName: "Blazor Web App", shortName: "blazor" }, 34 | { templateName: "Blazor WebAssembly App Empty", shortName: "blazorwasm-empty" }, 35 | { templateName: "Blazor WebAssembly Standalone App", shortName: "blazorwasm" }, 36 | ], 37 | cloud: [], // No specific templates for cloud in the given list 38 | console: [{ templateName: "Console App", shortName: "console" }], 39 | desktop: [ 40 | { templateName: "Windows Forms App", shortName: "winforms" }, 41 | { templateName: "Windows Forms Class Library", shortName: "winformslib" }, 42 | { templateName: "Windows Forms Control Library", shortName: "winformscontrollib" }, 43 | { templateName: "WPF Application", shortName: "wpf" }, 44 | { templateName: "WPF Class Library", shortName: "wpflib" }, 45 | { templateName: "WPF Custom Control Library", shortName: "wpfcustomcontrollib" }, 46 | { templateName: "WPF User Control Library", shortName: "wpfusercontrollib" }, 47 | ], 48 | extensions: [], // No specific templates for extensions in the given list 49 | game: [], // No specific templates for game in the given list 50 | iot: [], // No specific templates for IoT in the given list 51 | lib: [ 52 | { templateName: "Class Library", shortName: "classlib" }, 53 | { templateName: ".NET MAUI Class Library", shortName: "mauilib" }, 54 | { templateName: "Android Class Library", shortName: "androidlib" }, 55 | { templateName: "iOS Class Library", shortName: "ioslib" }, 56 | { templateName: "Mac Catalyst Class Library", shortName: "maccatalystlib" }, 57 | { templateName: "Razor Class Library", shortName: "razorclasslib" }, 58 | ], 59 | machinelearning: [], // No specific templates for machine learning in the given list 60 | maui: [ 61 | { templateName: ".NET MAUI App", shortName: "maui" }, 62 | { templateName: ".NET MAUI ContentPage (C#)", shortName: "maui-page-csharp" }, 63 | { templateName: ".NET MAUI ContentPage (XAML)", shortName: "maui-page-xaml" }, 64 | { templateName: ".NET MAUI ContentView (C#)", shortName: "maui-view-csharp" }, 65 | { templateName: ".NET MAUI ContentView (XAML)", shortName: "maui-view-xaml" }, 66 | { templateName: ".NET MAUI ResourceDictionary (XAML)", shortName: "maui-dict-xaml" }, 67 | ], 68 | mobile: [ 69 | { templateName: "Android Application", shortName: "android" }, 70 | { templateName: "Android Wear Application", shortName: "androidwear" }, 71 | { templateName: "iOS Application", shortName: "ios" }, 72 | { templateName: "iOS Tabbed Application", shortName: "ios-tabbed" }, 73 | ], 74 | test: [ 75 | { templateName: "MSTest Test Project", shortName: "mstest" }, 76 | { templateName: "MSTest Playwright Test Project", shortName: "mstest-playwright" }, 77 | { templateName: "NUnit 3 Test Project", shortName: "nunit" }, 78 | { templateName: "NUnit 3 Test Item", shortName: "nunit-test" }, 79 | { templateName: "NUnit Playwright Test Project", shortName: "nunit-playwright" }, 80 | { templateName: "xUnit Test Project", shortName: "xunit" }, 81 | ], 82 | web: [ 83 | { templateName: "ASP.NET Core Empty", shortName: "web" }, 84 | { templateName: "ASP.NET Core gRPC Service", shortName: "grpc" }, 85 | { templateName: "ASP.NET Core Web App (Model-View-Controller)", shortName: "mvc" }, 86 | { templateName: "ASP.NET Core Web App (Razor Pages)", shortName: "webapp" }, 87 | { templateName: "ASP.NET Core with Angular", shortName: "angular" }, 88 | { templateName: "ASP.NET Core with React.js", shortName: "react" }, 89 | { templateName: "ASP.NET Core with React.js and Redux", shortName: "reactredux" }, 90 | { templateName: "Razor Component", shortName: "razorcomponent" }, 91 | { templateName: "Razor Page", shortName: "page" }, 92 | { templateName: "Razor View", shortName: "view" }, 93 | ], 94 | }; 95 | 96 | function populateTemplateSelect(group) { 97 | const templates = projectGroupToTemplates[group] || []; 98 | 99 | template.innerHTML = templates 100 | .map( 101 | (template) => 102 | `` 103 | ) 104 | .join(""); 105 | } 106 | 107 | /* Project group select */ 108 | projectGroupSelect.addEventListener("change", () => { 109 | populateTemplateSelect(projectGroupSelect.value); 110 | }); 111 | 112 | function fieldValidation() { 113 | if (project.value === "") { 114 | buttonCreateProject.disabled = true; 115 | } else { 116 | buttonCreateProject.disabled = false; 117 | buttonCreateProject.style.backgroundColor = "#0E639C"; 118 | } 119 | } 120 | 121 | template.addEventListener("keydown" | "click", () => { 122 | project.focus(); 123 | }); 124 | 125 | project.addEventListener("change", () => { 126 | fieldValidation(); 127 | solution.value = project.value; 128 | }); 129 | 130 | // create console project 131 | buttonCreateProject.addEventListener("click", () => { 132 | let frameworkSelected = framework.options[framework.selectedIndex].value; 133 | 134 | // verify if project has white spaces 135 | let projectTrimmed = project.value.replace(/\s/g, ""); 136 | 137 | vscode.postMessage({ 138 | command: "addProject", 139 | template: template.options[template.selectedIndex].value, 140 | project: projectTrimmed, 141 | framework: frameworkSelected, 142 | }); 143 | }); 144 | })(); 145 | -------------------------------------------------------------------------------- /src/resource/contextualMenu/ContextualMenu.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as path from "path"; 3 | import * as fs from "fs"; 4 | import * as parentFinder from "find-parent-dir"; 5 | import * as os from "os"; 6 | const findUpGlob = require("find-up-glob"); 7 | const lineByLine = require("n-readlines"); 8 | 9 | class ContextualMenu { 10 | public static async init(uri: vscode.Uri, fileType: string, framework: string) { 11 | let pathSelected = ""; 12 | let projectRoot: string | undefined; 13 | 14 | // If the command is called from the explorer context menu (true) or from the editor context menu (false) 15 | const formContextMenu = !!uri; 16 | 17 | if (formContextMenu && uri) { 18 | pathSelected = uri.fsPath; 19 | projectRoot = getProjectRootDirOrFilePath(pathSelected); 20 | 21 | if (!projectRoot) { 22 | vscode.window.showErrorMessage("No project selected"); 23 | return; 24 | } 25 | } else { 26 | projectRoot = await selectProject(); 27 | 28 | if (!projectRoot) { 29 | vscode.window.showErrorMessage("No project selected"); 30 | return; 31 | } 32 | 33 | // Open folder picker at the root of the selected project 34 | const selectedFolder = await vscode.window.showOpenDialog({ 35 | canSelectFolders: true, 36 | canSelectFiles: false, 37 | canSelectMany: false, 38 | defaultUri: vscode.Uri.file(projectRoot), 39 | openLabel: "Select folder", 40 | }); 41 | 42 | if (!selectedFolder || selectedFolder.length === 0) { 43 | vscode.window.showErrorMessage("No folder selected"); 44 | return; 45 | } 46 | pathSelected = selectedFolder[0].fsPath; 47 | } 48 | 49 | // Resource creation 50 | vscode.window 51 | .showInputBox({ 52 | ignoreFocusOut: true, 53 | prompt: "Type the file name", 54 | value: "New " + fileType + ".cs", 55 | }) 56 | .then((newFileName) => { 57 | if (typeof newFileName === undefined || newFileName === "") { 58 | vscode.window.showErrorMessage( 59 | "Please input a valid name or press Scape to cancel the operation!" 60 | ); 61 | return this.init(uri, fileType, framework); 62 | } 63 | 64 | if (newFileName) { 65 | newFileName = newFileName.replace(/\s/g, ""); 66 | } else { 67 | newFileName = "New" + fileType + ".cs"; 68 | } 69 | 70 | let newFilePath = pathSelected + path.sep + newFileName; 71 | 72 | if (fs.existsSync(newFilePath)) { 73 | vscode.window.showErrorMessage(`File ${newFileName} already exist`); 74 | return; 75 | } 76 | 77 | newFilePath = correctFileNameExtension(newFilePath); 78 | let originalFilePath = newFilePath; 79 | 80 | let rootDir = getProjectRootDirOrFilePath(newFilePath); 81 | 82 | if (rootDir === null) { 83 | vscode.window.showErrorMessage("Unable to find *.csproj or project.json"); 84 | return; 85 | } 86 | 87 | let rootNamespace: any = checkRootNameOnCsproj(newFilePath); 88 | 89 | if (rootDir !== undefined) { 90 | rootDir = 91 | rootDir[rootDir.length - 1] === path.sep 92 | ? rootDir.substring(0, rootDir.length - 1) 93 | : rootDir; 94 | 95 | let projRootDir = rootDir.substring(rootDir.lastIndexOf(path.sep) + 1); 96 | 97 | let childFilePath = newFilePath.substring(newFilePath.lastIndexOf(projRootDir)); 98 | 99 | if (rootNamespace !== null) { 100 | childFilePath = childFilePath.replace( 101 | childFilePath.substring(0, childFilePath.indexOf("\\")), 102 | rootNamespace 103 | ); 104 | } 105 | 106 | // set the regex pattern for path structure 107 | let pathSepRegEX = /\//g; 108 | if (os.platform() === "win32") { 109 | pathSepRegEX = /\\/g; 110 | } 111 | 112 | // replace \\ for . in following the namespace convention 113 | let namespace = path.dirname(childFilePath); 114 | namespace = namespace.replace(pathSepRegEX, "."); 115 | 116 | // replace file name empty space or dash by underscore 117 | namespace = namespace.replace(/\s+/g, "_"); 118 | namespace = namespace.replace(/-/g, "_"); 119 | 120 | newFilePath = path.basename(newFilePath, ".cs"); 121 | 122 | loadTemplate(fileType, namespace, newFilePath, originalFilePath, framework); 123 | } 124 | }); 125 | } 126 | } 127 | 128 | export { ContextualMenu }; 129 | 130 | // function to fix the file extension in case user forget to input .cs 131 | function correctFileNameExtension(fileName: any) { 132 | if (path.extname(fileName) !== ".cs") { 133 | if (fileName.endsWith(".")) { 134 | fileName = fileName + "cs"; 135 | } else { 136 | fileName = fileName + ".cs"; 137 | } 138 | } 139 | return fileName; 140 | } 141 | 142 | // function to detect the root directory where the .csproj is included 143 | function getProjectRootDirOrFilePath(filePath: any): string | undefined { 144 | var projectRootDir = parentFinder.sync(path.dirname(filePath), "project.json"); 145 | if (projectRootDir === null) { 146 | let csProjFiles = findUpGlob.sync("*.csproj", { cwd: path.dirname(filePath) }); 147 | 148 | if (csProjFiles === null) { 149 | return undefined; 150 | } 151 | projectRootDir = path.dirname(csProjFiles[0]); 152 | } 153 | return projectRootDir; 154 | } 155 | 156 | // load the template, replace by current values and create the document in the folder selected 157 | function loadTemplate( 158 | templateType: string, 159 | namespace: string, 160 | newFilepath: string, 161 | originalFilePath: string, 162 | framework: string 163 | ) { 164 | let fileTemplate: string = ""; 165 | 166 | if (framework === "net6.0") { 167 | fileTemplate = templateType + "6.mdl"; 168 | } else { 169 | fileTemplate = templateType + ".mdl"; 170 | } 171 | 172 | vscode.workspace 173 | .openTextDocument( 174 | vscode.extensions.getExtension("richardzampieriprog.csharp-snippet-productivity") 175 | ?.extensionPath + 176 | "/models/" + 177 | fileTemplate 178 | ) 179 | .then((doc: vscode.TextDocument) => { 180 | let content = doc.getText(); 181 | content = content.replace("${namespace}", namespace); 182 | content = content.replace("${fileName}", newFilepath); 183 | let cursorPos = findCursorPos(content); 184 | content = content.replace("${cursor}", ""); 185 | fs.writeFileSync(originalFilePath, content); 186 | 187 | vscode.workspace.openTextDocument(originalFilePath).then((doc) => { 188 | vscode.window.showTextDocument(doc).then((editor) => { 189 | let selection = new vscode.Selection(cursorPos, cursorPos); 190 | editor.selection = selection; 191 | }); 192 | }); 193 | }); 194 | } 195 | 196 | // find cursor position in the template file 197 | function findCursorPos(content: string) { 198 | let cursorPos = content.indexOf("${cursor}"); 199 | let beforePos = content.substring(0, cursorPos); 200 | let line = beforePos.match(/\n/gi)?.length; 201 | let charId = beforePos.substring(beforePos.lastIndexOf("\n")).length; 202 | return new vscode.Position(line !== undefined ? line : 0, charId); 203 | } 204 | 205 | // function to check root namespace in the csproj file 206 | function checkRootNameOnCsproj(filePath: string) { 207 | let rootNamespace: string = findUpGlob.sync("*.csproj", { cwd: path.dirname(filePath) })[0]; 208 | const liner = new lineByLine(rootNamespace); 209 | let line; 210 | 211 | while ((line = liner.next())) { 212 | let l = line.toString("ascii"); 213 | let result: string = l.match(/(.*?)<\/RootNamespace>/g); 214 | if (result === null) { 215 | continue; 216 | } 217 | let content = result[0]; 218 | 219 | let root: string = content.substring(content.indexOf(">") + 1, content.indexOf(" { 231 | const solutionFolders = vscode.workspace.workspaceFolders; 232 | if (!solutionFolders || solutionFolders.length === 0) { 233 | return undefined; 234 | } 235 | 236 | const csprojFiles = await vscode.workspace.findFiles("**/*.csproj"); 237 | const projects = csprojFiles.map((file) => { 238 | return { 239 | label: path.basename(file.fsPath), 240 | detail: path.dirname(file.fsPath), 241 | }; 242 | }); 243 | 244 | const selectedProject = await vscode.window.showQuickPick(projects, { 245 | placeHolder: "Select a project", 246 | }); 247 | 248 | return selectedProject?.detail; 249 | } 250 | -------------------------------------------------------------------------------- /src/resource/addProjectToSolution/Panel.ts: -------------------------------------------------------------------------------- 1 | import { 2 | window, 3 | Uri, 4 | ViewColumn, 5 | WebviewPanel, 6 | ExtensionContext, 7 | WebviewOptions, 8 | workspace, 9 | } from "vscode"; 10 | import * as path from "path"; 11 | import * as fs from "fs"; 12 | import { getTargetFrameworks } from "../../utils/sdk.provider"; 13 | import { CommandFactory, TEMPLATE_COMPATIBILITY } from "../../utils/terminal-cmd.provider"; 14 | 15 | type FrameworkCommand = { 16 | [key: string]: string; 17 | }; 18 | 19 | const frameworkCommands: FrameworkCommand = { 20 | ["3.1"]: "netcoreapp3.1", 21 | ["5.0"]: "net5.0", 22 | ["6.0"]: "net6.0", 23 | ["7.0"]: "net7.0", 24 | ["8.0"]: "net8.0", 25 | }; 26 | 27 | export class Panel { 28 | readonly _context: ExtensionContext; 29 | private _webViewPanel: WebviewPanel | undefined = undefined; 30 | private _vscodeCss!: Uri; 31 | private _resetCss!: Uri; 32 | private _script!: Uri; 33 | private _projectName: string; 34 | private _solution: string; 35 | private _sdksResources!: Uri; 36 | private _sdks!: string[]; 37 | 38 | constructor( 39 | context: ExtensionContext, 40 | projectName: string, 41 | solution: string, 42 | title: string, 43 | iconPath: { folder: string; file: string } = { folder: "", file: "" }, 44 | viewColumn: ViewColumn = ViewColumn.One, 45 | preserveFocus: boolean = false, 46 | enableFindWidget: boolean = false, 47 | retainContextWhenHidden: boolean = false 48 | ) { 49 | // context from the extension main entry point 50 | this._context = context; 51 | this._projectName = projectName; 52 | this._solution = solution; 53 | 54 | let _viewType: string = title.replace(/[^A-Z0-9]/gi, "-"); 55 | 56 | if (this._webViewPanel) { 57 | this._webViewPanel.reveal(window.activeTextEditor?.viewColumn); 58 | } else { 59 | // creating the panel 60 | this._webViewPanel = window.createWebviewPanel( 61 | _viewType, 62 | title, 63 | { preserveFocus, viewColumn }, 64 | { enableFindWidget, retainContextWhenHidden } 65 | ); 66 | this._webViewPanel.iconPath = Uri.file( 67 | path.join(this._context.extensionPath, iconPath.folder, iconPath.file) 68 | ); 69 | 70 | // webview options 71 | this._webViewPanel.webview.options = { 72 | enableCommandUris: false, 73 | enableScripts: true, 74 | localResourceRoots: [ 75 | Uri.file(path.join(this._context.extensionPath, "media")), 76 | Uri.file(path.join(this._context.extensionPath, "out")), 77 | ], 78 | portMapping: [], 79 | }; 80 | 81 | this._sdksResources = this._webViewPanel.webview.asWebviewUri( 82 | Uri.file(path.join(this._context.extensionPath, "media", "sdks.txt")) 83 | ); 84 | this._sdks = getTargetFrameworks(this._sdksResources); 85 | this._resetCss = this._webViewPanel.webview.asWebviewUri( 86 | Uri.file(path.join(this._context.extensionPath, "media", "reset.css")) 87 | ); 88 | this._vscodeCss = this._webViewPanel.webview.asWebviewUri( 89 | Uri.file(path.join(this._context.extensionPath, "media", "vscode.css")) 90 | ); 91 | this._script = this._webViewPanel.webview.asWebviewUri( 92 | Uri.file(path.join(this._context.extensionPath, "media", "addProject.js")) 93 | ); 94 | this._webViewPanel.webview.html = this.baseHtml( 95 | "index.html", 96 | this._resetCss, 97 | this._vscodeCss, 98 | this._script 99 | ); 100 | } 101 | 102 | // control event listener 103 | this._webViewPanel.webview.onDidReceiveMessage(async (message) => { 104 | switch (message.command) { 105 | case "addProject": 106 | await this.addProject(message); 107 | return; 108 | } 109 | }); 110 | } 111 | 112 | private async addProject(message: any): Promise { 113 | let root = workspace.workspaceFolders 114 | ?.map((folder) => folder.uri.path)[0] 115 | .replace(/\//g, "\\"); 116 | root = root?.slice(1, root.length); 117 | 118 | // Get the framework command 119 | message.framework = frameworkCommands[message.framework]; 120 | 121 | const terminal = window.createTerminal(); 122 | terminal.show(true); 123 | 124 | if (!isFrameworkCompatible(message)) { 125 | // Format list of compatible frameworks 126 | const compatibleFrameworks = TEMPLATE_COMPATIBILITY[message.template] 127 | .map((f) => `'${f.substring(3)}'`) 128 | .join(", "); 129 | 130 | window.showWarningMessage( 131 | `Please select a compatible framework for ${message.template} - [${ 132 | compatibleFrameworks || "None" 133 | }]` 134 | ); 135 | 136 | setTimeout(() => { 137 | terminal.dispose(); 138 | }, 5000); 139 | 140 | return; 141 | } 142 | 143 | terminal.sendText( 144 | "dotnet new " + 145 | message.template + 146 | " --language c# -n " + 147 | message.project + 148 | " -o " + 149 | root + 150 | "\\" + 151 | message.project + 152 | " -f " + 153 | message.framework + 154 | " --force" 155 | ); 156 | terminal.sendText( 157 | "dotnet sln " + 158 | this._solution + 159 | " add " + 160 | root + 161 | "\\" + 162 | message.project + 163 | "\\" + 164 | message.project + 165 | ".csproj" 166 | ); 167 | 168 | setTimeout(() => { 169 | terminal.dispose(); 170 | }, 5000); 171 | 172 | // Close the WebView after the project is created 173 | if (this._webViewPanel) { 174 | this._webViewPanel.dispose(); 175 | this._webViewPanel = undefined; 176 | } 177 | } 178 | 179 | private getNonce() { 180 | let text = ""; 181 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 182 | for (let i: number = 0; i < 32; i++) { 183 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 184 | } 185 | return text; 186 | } 187 | 188 | public get webViewPanel(): WebviewPanel | undefined { 189 | return this._webViewPanel; 190 | } 191 | public set webViewPanel(panelInstance: WebviewPanel | undefined) { 192 | this._webViewPanel = panelInstance; 193 | } 194 | 195 | public set iconPath( 196 | icon: { folder: string; file: string } | { folder: string; file: string }[] 197 | ) { 198 | if (this._webViewPanel) { 199 | if (Array.isArray(icon)) { 200 | this._webViewPanel.iconPath = { 201 | dark: Uri.file( 202 | path.join(this._context.extensionPath, icon[0].folder, icon[0].file) 203 | ), 204 | light: Uri.file( 205 | path.join(this._context.extensionPath, icon[1].folder, icon[1].file) 206 | ), 207 | }; 208 | } else { 209 | this._webViewPanel.iconPath = Uri.file( 210 | path.join(this._context.extensionPath, icon.folder, icon.file) 211 | ); 212 | } 213 | } 214 | } 215 | 216 | public set options(options: WebviewOptions) { 217 | if (this._webViewPanel) { 218 | this._webViewPanel.webview.options = options; 219 | } 220 | } 221 | public allowedLocalResource(...folders: string[]) { 222 | if (this._webViewPanel) { 223 | let foldersRoot: Uri[] = []; 224 | 225 | for (let i: number = 0; i < folders.length; i++) { 226 | foldersRoot[i] = Uri.file(path.join(this._context.extensionPath, folders[i])); 227 | } 228 | 229 | this._webViewPanel.webview.options = { 230 | localResourceRoots: foldersRoot, 231 | }; 232 | } 233 | } 234 | 235 | public set html(htmlDoc: string) { 236 | if (this._webViewPanel) { 237 | this._webViewPanel.webview.html = htmlDoc; 238 | } 239 | } 240 | 241 | public addResource(content: { folder: string; resource: string }): Uri | undefined { 242 | const diskResource = Uri.file( 243 | path.join(this._context.extensionPath, content.folder, content.resource) 244 | ); 245 | return this._webViewPanel?.webview.asWebviewUri(diskResource); 246 | } 247 | 248 | private baseHtml(page: string, ...resource: Uri[]): string { 249 | let html = fs.readFileSync(path.join(this._context.extensionPath, "media", page), "utf-8"); 250 | html = html.replace(`{{project}}`, this._projectName); 251 | html = html.replace(`{{stylesResetUri}}`, resource[0].toString()); 252 | html = html.replace(`{{stylesMainUri}}`, resource[1].toString()); 253 | html = html.replace(`{{script}}`, resource[2].toString()); 254 | 255 | const sdkOptions = this._sdks 256 | .map((sdk) => ``) 257 | .join(""); 258 | 259 | html = html.replace(`{{sdkOptions}}`, sdkOptions); 260 | 261 | if (this._webViewPanel) { 262 | html = html.split(`{{nonce}}`).join(this.getNonce()); 263 | html = html.split(`{{cspSource}}`).join(this._webViewPanel.webview.cspSource); 264 | } 265 | return html.toString(); 266 | } 267 | } 268 | 269 | function isFrameworkCompatible(message: any) { 270 | // Verify if template is not undefined 271 | if (!message.template || !(message.template in TEMPLATE_COMPATIBILITY)) { 272 | return true; 273 | } 274 | return TEMPLATE_COMPATIBILITY[message.template].includes(message.framework); 275 | } 276 | -------------------------------------------------------------------------------- /media/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable curly */ 2 | // This script will be run within the webview itself 3 | // It cannot access the main VS Code APIs directly. 4 | 5 | (function () { 6 | // vscode api 7 | const vscode = acquireVsCodeApi(); 8 | 9 | // Html elements bindings 10 | const buttonCreateProject = document.getElementById("create-project-button"); 11 | const buttonFilePicker = document.getElementById("selectFolder"); 12 | const projectGroupSelect = document.getElementById("project-group-select"); 13 | const template = document.getElementById("custom-select"); 14 | const project = document.getElementById("projectName"); 15 | const filePath = document.getElementById("inputLocal"); 16 | const solution = document.getElementById("solution"); 17 | const framework = document.getElementById("custom-select2"); 18 | 19 | document.addEventListener("DOMContentLoaded", function (event) { 20 | buttonCreateProject.disabled = "true"; 21 | buttonCreateProject.style.backgroundColor = "#3C3C3C"; 22 | 23 | // Event listener for project group selection 24 | projectGroupSelect.addEventListener("change", function () { 25 | loadTemplateSelect(this.value, ""); 26 | }); 27 | 28 | fieldValidation(); 29 | }); 30 | 31 | function fieldValidation() { 32 | if (project.value === "" || solution.value === "") { 33 | buttonCreateProject.disabled = true; 34 | } else { 35 | buttonCreateProject.disabled = false; 36 | buttonCreateProject.style.backgroundColor = "#0E639C"; 37 | } 38 | } 39 | 40 | /* Project group select */ 41 | const projectGroupToTemplates = { 42 | api: [ 43 | { templateName: ".NET Core Web API", shortName: "webapi" }, 44 | { templateName: ".NET Core Web API (native AOT)", shortName: "webapiaot" }, 45 | { templateName: "API Controller", shortName: "apicontroller" }, 46 | ], 47 | blazor: [ 48 | { templateName: ".NET MAUI Blazor Hybrid App", shortName: "maui-blazor" }, 49 | { templateName: "Blazor Server App", shortName: "blazorserver" }, 50 | { templateName: "Blazor Server App Empty", shortName: "blazorserver-empty" }, 51 | { templateName: "Blazor Web App", shortName: "blazor" }, 52 | { templateName: "Blazor WebAssembly App Empty", shortName: "blazorwasm-empty" }, 53 | { templateName: "Blazor WebAssembly Standalone App", shortName: "blazorwasm" }, 54 | ], 55 | cloud: [], // No specific templates for cloud in the given list 56 | console: [{ templateName: "Console App", shortName: "console" }], 57 | desktop: [ 58 | { templateName: "Windows Forms App", shortName: "winforms" }, 59 | { templateName: "Windows Forms Class Library", shortName: "winformslib" }, 60 | { templateName: "Windows Forms Control Library", shortName: "winformscontrollib" }, 61 | { templateName: "WPF Application", shortName: "wpf" }, 62 | { templateName: "WPF Class Library", shortName: "wpflib" }, 63 | { templateName: "WPF Custom Control Library", shortName: "wpfcustomcontrollib" }, 64 | { templateName: "WPF User Control Library", shortName: "wpfusercontrollib" }, 65 | ], 66 | extensions: [], // No specific templates for extensions in the given list 67 | game: [], // No specific templates for game in the given list 68 | iot: [], // No specific templates for IoT in the given list 69 | lib: [ 70 | { templateName: "Class Library", shortName: "classlib" }, 71 | { templateName: ".NET MAUI Class Library", shortName: "mauilib" }, 72 | { templateName: "Android Class Library", shortName: "androidlib" }, 73 | { templateName: "iOS Class Library", shortName: "ioslib" }, 74 | { templateName: "Mac Catalyst Class Library", shortName: "maccatalystlib" }, 75 | { templateName: "Razor Class Library", shortName: "razorclasslib" }, 76 | ], 77 | machinelearning: [], // No specific templates for machine learning in the given list 78 | maui: [ 79 | { templateName: ".NET MAUI App", shortName: "maui" }, 80 | { templateName: ".NET MAUI ContentPage (C#)", shortName: "maui-page-csharp" }, 81 | { templateName: ".NET MAUI ContentPage (XAML)", shortName: "maui-page-xaml" }, 82 | { templateName: ".NET MAUI ContentView (C#)", shortName: "maui-view-csharp" }, 83 | { templateName: ".NET MAUI ContentView (XAML)", shortName: "maui-view-xaml" }, 84 | { templateName: ".NET MAUI ResourceDictionary (XAML)", shortName: "maui-dict-xaml" }, 85 | ], 86 | mobile: [ 87 | { templateName: "Android Application", shortName: "android" }, 88 | { templateName: "Android Wear Application", shortName: "androidwear" }, 89 | { templateName: "iOS Application", shortName: "ios" }, 90 | { templateName: "iOS Tabbed Application", shortName: "ios-tabbed" }, 91 | ], 92 | test: [ 93 | { templateName: "MSTest Test Project", shortName: "mstest" }, 94 | { templateName: "MSTest Playwright Test Project", shortName: "mstest-playwright" }, 95 | { templateName: "NUnit 3 Test Project", shortName: "nunit" }, 96 | { templateName: "NUnit 3 Test Item", shortName: "nunit-test" }, 97 | { templateName: "NUnit Playwright Test Project", shortName: "nunit-playwright" }, 98 | { templateName: "xUnit Test Project", shortName: "xunit" }, 99 | ], 100 | web: [ 101 | { templateName: "ASP.NET Core Empty", shortName: "web" }, 102 | { templateName: "ASP.NET Core gRPC Service", shortName: "grpc" }, 103 | { templateName: "ASP.NET Core Web App (Model-View-Controller)", shortName: "mvc" }, 104 | { templateName: "ASP.NET Core Web App (Razor Pages)", shortName: "webapp" }, 105 | { templateName: "ASP.NET Core with Angular", shortName: "angular" }, 106 | { templateName: "ASP.NET Core with React.js", shortName: "react" }, 107 | { templateName: "ASP.NET Core with React.js and Redux", shortName: "reactredux" }, 108 | { templateName: "Razor Component", shortName: "razorcomponent" }, 109 | { templateName: "Razor Page", shortName: "page" }, 110 | { templateName: "Razor View", shortName: "view" }, 111 | ], 112 | }; 113 | 114 | // Load template select based on selected project group 115 | function loadTemplateSelect(group, selectedTemplate) { 116 | template.innerHTML = ""; // Clear existing options 117 | 118 | // Add default 'Select Template' option 119 | const defaultOption = document.createElement("option"); 120 | defaultOption.value = ""; 121 | defaultOption.textContent = "Select Template"; 122 | template.appendChild(defaultOption); 123 | 124 | // Check if a project group is selected before populating templates 125 | if (group && projectGroupToTemplates[group]) { 126 | const templates = projectGroupToTemplates[group]; 127 | templates.forEach((tmpl) => { 128 | const option = document.createElement("option"); 129 | option.value = tmpl.shortName; 130 | option.textContent = tmpl.templateName; 131 | if (tmpl.shortName === selectedTemplate) option.selected = true; 132 | template.appendChild(option); 133 | }); 134 | } 135 | } 136 | 137 | // Receive message from the extension 138 | window.addEventListener("message", (event) => { 139 | const message = event.data; // The JSON data our extension sent 140 | 141 | switch (message.command) { 142 | case "updateState": 143 | projectGroupSelect.value = message.projectGroup; 144 | loadTemplateSelect(message.projectGroup, message.selectedTemplate); 145 | break; 146 | } 147 | }); 148 | /* Project group select End of Implementation */ 149 | 150 | template.addEventListener("keydown" | "click", () => { 151 | project.focus(); 152 | }); 153 | 154 | project.addEventListener("change", () => { 155 | solution.value = project.value; 156 | fieldValidation(); 157 | }); 158 | 159 | solution.addEventListener("keyup", () => { 160 | fieldValidation(); 161 | }); 162 | 163 | filePath.addEventListener("keyup" | "focus", () => { 164 | fieldValidation(); 165 | }); 166 | 167 | filePath.addEventListener("keydown", () => { 168 | buttonFilePicker.focus(); 169 | }); 170 | 171 | // create console project 172 | buttonCreateProject.addEventListener("click", () => { 173 | let frameworkSelected = framework.options[framework.selectedIndex].value; 174 | let frameworkRun = ""; 175 | 176 | if (frameworkSelected === "2.0") frameworkRun = "netcoreapp2.0"; 177 | else if (frameworkSelected === "2.1") frameworkRun = "netcoreapp2.1"; 178 | else if (frameworkSelected === "2.2") frameworkRun = "netcoreapp2.2"; 179 | else if (frameworkSelected === "3.0") frameworkRun = "netcoreapp3.0"; 180 | else if (frameworkSelected === "3.1") frameworkRun = "netcoreapp3.1"; 181 | else if (frameworkSelected === "5.0") frameworkRun = "net5.0"; 182 | else if (frameworkSelected === "6.0") frameworkRun = "net6.0"; 183 | else if (frameworkSelected === "7.0") frameworkRun = "net7.0"; 184 | else if (frameworkSelected === "8.0") frameworkRun = "net8.0"; 185 | 186 | vscode.postMessage({ 187 | command: "createProject", 188 | template: template.options[template.selectedIndex].value, 189 | project: project.value, 190 | filePath: filePath.value, 191 | solution: solution.value, 192 | framework: frameworkRun, 193 | }); 194 | }); 195 | 196 | // file picker to save the project in a specific location 197 | buttonFilePicker.addEventListener("click", () => { 198 | solution.focus(); 199 | vscode.postMessage({ 200 | command: "selectDirectory", 201 | projectGroupSelect: 202 | projectGroupSelect.options[projectGroupSelect.selectedIndex].textContent, 203 | templateName: template.options[template.selectedIndex].text, 204 | template: template.options[template.selectedIndex].value, 205 | project: project.value, 206 | solution: solution.value, 207 | framework: framework.options[framework.selectedIndex].value, 208 | }); 209 | }); 210 | })(); 211 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csharp-snippet-productivity", 3 | "displayName": "C# Toolbox of Productivity", 4 | "description": "The complete set of tools for C# development", 5 | "version": "2.1.1", 6 | "icon": "icon.png", 7 | "publisher": "richardzampieriprog", 8 | "license": "SEE LICENSE IN LICENSE.md", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/rsaz/csharp-snippet-productivity" 12 | }, 13 | "engines": { 14 | "vscode": "^1.85.0" 15 | }, 16 | "categories": [ 17 | "Programming Languages", 18 | "Snippets", 19 | "Formatters" 20 | ], 21 | "keywords": [ 22 | ".NET", 23 | ".NET Core", 24 | "C#", 25 | "Visual Studio", 26 | "snippet", 27 | "productivity", 28 | "keybindings" 29 | ], 30 | "extensionKind": [ 31 | "ui" 32 | ], 33 | "activationEvents": [ 34 | "onLanguage:csharp", 35 | "onLanguage:markdown", 36 | "onLanguage:plaintext" 37 | ], 38 | "contributes": { 39 | "snippets": [ 40 | { 41 | "language": "csharp", 42 | "path": "./snippets/general.json" 43 | }, 44 | { 45 | "language": "csharp", 46 | "path": "./snippets/documentxml.json" 47 | }, 48 | { 49 | "language": "csharp", 50 | "path": "./snippets/designpattern.json" 51 | } 52 | ], 53 | "commands": [ 54 | { 55 | "command": "csharp-snippet-productivity.createProject", 56 | "title": "Create Project", 57 | "category": "C# Toolbox" 58 | }, 59 | { 60 | "command": "csharp-snippet-productivity.createClass", 61 | "title": "Create Class", 62 | "category": "C# Toolbox" 63 | }, 64 | { 65 | "command": "csharp-snippet-productivity.createInterface", 66 | "title": "Create Interface", 67 | "category": "C# Toolbox" 68 | }, 69 | { 70 | "command": "csharp-snippet-productivity.createStruct", 71 | "title": "Create Struct", 72 | "category": "C# Toolbox" 73 | }, 74 | { 75 | "command": "csharp-snippet-productivity.createRecord", 76 | "title": "Create Record", 77 | "category": "C# Toolbox" 78 | }, 79 | { 80 | "command": "csharp-snippet-productivity.addProjectToSolution", 81 | "title": "Add Project", 82 | "category": "C# Toolbox" 83 | }, 84 | { 85 | "command": "csharp-snippet-productivity.scaffoldResources", 86 | "title": "Scaffold Resources", 87 | "category": "C# Toolbox" 88 | } 89 | ], 90 | "keybindings": [ 91 | { 92 | "command": "csharp-snippet-productivity.createProject", 93 | "key": "ctrl+alt+/ p" 94 | }, 95 | { 96 | "command": "csharp-snippet-productivity.addProjectToSolution", 97 | "key": "ctrl+alt+/ a" 98 | }, 99 | { 100 | "command": "csharp-snippet-productivity.createClass", 101 | "key": "ctrl+alt+/ c", 102 | "when": "!editorReadonly" 103 | }, 104 | { 105 | "command": "csharp-snippet-productivity.createInterface", 106 | "key": "ctrl+alt+/ i", 107 | "when": "!editorReadonly" 108 | }, 109 | { 110 | "command": "csharp-snippet-productivity.createStruct", 111 | "key": "ctrl+alt+/ s", 112 | "when": "!editorReadonly" 113 | }, 114 | { 115 | "command": "csharp-snippet-productivity.createRecord", 116 | "key": "ctrl+alt+/ r", 117 | "when": "!editorReadonly" 118 | }, 119 | { 120 | "command": "csharp-snippet-productivity.scaffoldResources", 121 | "key": "ctrl+alt+/ ctrl+e", 122 | "when": "!editorReadonly" 123 | } 124 | ], 125 | "menus": { 126 | "explorer/context": [ 127 | { 128 | "submenu": "cSharp.subMenu", 129 | "group": "navigation@1" 130 | } 131 | ], 132 | "cSharp.subMenu": [ 133 | { 134 | "command": "csharp-snippet-productivity.createClass" 135 | }, 136 | { 137 | "command": "csharp-snippet-productivity.createInterface" 138 | }, 139 | { 140 | "command": "csharp-snippet-productivity.createStruct" 141 | }, 142 | { 143 | "command": "csharp-snippet-productivity.createRecord" 144 | }, 145 | { 146 | "command": "csharp-snippet-productivity.addProjectToSolution" 147 | }, 148 | { 149 | "command": "csharp-snippet-productivity.scaffoldResources" 150 | } 151 | ], 152 | "commandPalette": [ 153 | { 154 | "command": "csharp-snippet-productivity.createProject", 155 | "when": "true" 156 | }, 157 | { 158 | "command": "csharp-snippet-productivity.createClass", 159 | "when": "true" 160 | }, 161 | { 162 | "command": "csharp-snippet-productivity.createInterface", 163 | "when": "true" 164 | }, 165 | { 166 | "command": "csharp-snippet-productivity.createStruct", 167 | "when": "true" 168 | }, 169 | { 170 | "command": "csharp-snippet-productivity.createRecord", 171 | "when": "true" 172 | }, 173 | { 174 | "command": "csharp-snippet-productivity.addProjectToSolution", 175 | "when": "true" 176 | }, 177 | { 178 | "command": "csharp-snippet-productivity.scaffoldResources", 179 | "when": "true" 180 | } 181 | ] 182 | }, 183 | "submenus": [ 184 | { 185 | "id": "cSharp.subMenu", 186 | "label": "C# Toolbox: Options" 187 | } 188 | ], 189 | "configuration": { 190 | "title": "C# Toolbox of Productivity", 191 | "properties": { 192 | "csharp-snippet-productivity.defaultFolderForProjectCreation": { 193 | "type": [ 194 | "string", 195 | "null" 196 | ], 197 | "description": "Set the default folder for project creation", 198 | "default": null 199 | }, 200 | "csharp-snippet-productivity.multilineComments": { 201 | "type": "boolean", 202 | "description": "Whether the multiline comment highlighter should be active", 203 | "default": true 204 | }, 205 | "csharp-snippet-productivity.highlightPlainText": { 206 | "type": "boolean", 207 | "description": "Whether the plaintext comment highlighter should be active", 208 | "default": false 209 | }, 210 | "csharp-snippet-productivity.tags": { 211 | "type": "array", 212 | "description": "Colored comments. Select your favorite colors. Changes require a restart of VS Code to take effect", 213 | "default": [ 214 | { 215 | "tag": "bug", 216 | "color": "#FF2D00", 217 | "strikethrough": false, 218 | "underline": false, 219 | "backgroundColor": "transparent", 220 | "bold": false, 221 | "italic": false 222 | }, 223 | { 224 | "tag": "research", 225 | "color": "#3498DB", 226 | "strikethrough": false, 227 | "underline": false, 228 | "backgroundColor": "transparent", 229 | "bold": false, 230 | "italic": false 231 | }, 232 | { 233 | "tag": "//", 234 | "color": "#474747", 235 | "strikethrough": true, 236 | "underline": false, 237 | "backgroundColor": "transparent", 238 | "bold": false, 239 | "italic": false 240 | }, 241 | { 242 | "tag": "todo", 243 | "color": "#FF8C00", 244 | "strikethrough": false, 245 | "underline": false, 246 | "backgroundColor": "transparent", 247 | "bold": false, 248 | "italic": false 249 | }, 250 | { 251 | "tag": "review", 252 | "color": "#B429A9", 253 | "strikethrough": false, 254 | "underline": false, 255 | "backgroundColor": "transparent", 256 | "bold": false, 257 | "italic": false 258 | } 259 | ] 260 | } 261 | } 262 | } 263 | }, 264 | "galleryBanner": { 265 | "color": "#e3f4ff", 266 | "theme": "light" 267 | }, 268 | "main": "./out/extension.js", 269 | "scripts": { 270 | "vscode:prepublish": "npm run compile", 271 | "compile": "tsc -p ./", 272 | "watch": "tsc -watch -p ./", 273 | "pretest": "npm run compile && npm run lint", 274 | "lint": "eslint src --ext ts", 275 | "test": "node ./out/test/runTest.js", 276 | "package": "vsce package" 277 | }, 278 | "devDependencies": { 279 | "@types/find-parent-dir": "^0.3.3", 280 | "@types/glob": "^8.1.0", 281 | "@types/mocha": "^10.0.6", 282 | "@types/node": "^20.11.0", 283 | "@types/vscode": "^1.85.0", 284 | "@typescript-eslint/eslint-plugin": "^6.18.1", 285 | "@typescript-eslint/parser": "^6.18.1", 286 | "eslint": "^8.56.0", 287 | "glob": "^10.3.10", 288 | "mocha": "^10.2.0", 289 | "typescript": "^5.3.3", 290 | "vscode-test": "^1.5.0" 291 | }, 292 | "dependencies": { 293 | "find-parent-dir": "^0.3.1", 294 | "find-up-glob": "^1.0.0", 295 | "n-readlines": "^1.0.1", 296 | "os": "^0.1.2" 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/resource/createProjectWebView/CreateProject.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as vscode from "vscode"; 4 | import { getTargetFrameworks } from "../../utils/sdk.provider"; 5 | import { getNonce } from "./GetNonce"; 6 | import { CommandFactory, Message } from "../../utils/terminal-cmd.provider"; 7 | 8 | export class CreateProjectPanel { 9 | private static context: vscode.ExtensionContext; 10 | private static _filepath: any = ""; 11 | private static _panel: vscode.WebviewPanel; 12 | private static _disposables: vscode.Disposable[] = []; 13 | private static _sdks: string[] = []; 14 | private static _defaultFolder: vscode.WorkspaceConfiguration | undefined; 15 | private static _terminal: vscode.Terminal; 16 | 17 | // To avoid direct instantiation use the createOrShow method 18 | private constructor() {} 19 | 20 | // Main method to create or show the panel 21 | public static createOrShow(context: vscode.ExtensionContext): void { 22 | this.context = context; 23 | 24 | const column = vscode.window.activeTextEditor 25 | ? vscode.window.activeTextEditor.viewColumn 26 | : undefined; 27 | 28 | // If we already have a panel, show it. 29 | if (CreateProjectPanel._panel) { 30 | CreateProjectPanel._panel.reveal(column); 31 | CreateProjectPanel.update(); 32 | return; 33 | } 34 | 35 | // Otherwise, create a new panel. 36 | const panel = vscode.window.createWebviewPanel( 37 | "create-project", 38 | "Create Project", 39 | column || vscode.ViewColumn.One, 40 | { 41 | enableScripts: true, 42 | localResourceRoots: [ 43 | vscode.Uri.joinPath(context.extensionUri, "media"), 44 | vscode.Uri.joinPath(context.extensionUri, "out/compiled"), 45 | ], 46 | } 47 | ); 48 | 49 | // adding panel icon 50 | panel.iconPath = vscode.Uri.file( 51 | path.join(this.context.extensionPath, "media", "addProjectIcon.png") 52 | ); 53 | 54 | CreateProjectPanel.defaultConstructor(panel); 55 | } 56 | 57 | private static async defaultConstructor(panel: vscode.WebviewPanel) { 58 | this._panel = panel; 59 | 60 | this._defaultFolder = vscode.workspace 61 | .getConfiguration("csharp-snippet-productivity") 62 | .get("defaultFolderForProjectCreation"); 63 | 64 | if (!this._defaultFolder) { 65 | vscode.window.showInformationMessage( 66 | "Please set a default folder for project creation" 67 | ); 68 | } 69 | 70 | this._filepath = this._defaultFolder; 71 | this._terminal = 72 | vscode.window.activeTerminal === undefined 73 | ? vscode.window.createTerminal() 74 | : vscode.window.activeTerminal; 75 | this._terminal.show(); 76 | 77 | // OnPanel Close 78 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 79 | 80 | this._panel.webview.onDidReceiveMessage( 81 | async (message) => { 82 | switch (message.command) { 83 | case "createProject": 84 | await this.projectCreation(message); 85 | return; 86 | 87 | case "selectDirectory": 88 | const options: vscode.OpenDialogOptions = { 89 | canSelectMany: false, 90 | openLabel: "Select", 91 | canSelectFiles: false, 92 | canSelectFolders: true, 93 | }; 94 | vscode.window.showOpenDialog(options).then((fileUri) => { 95 | if (fileUri && fileUri[0]) { 96 | this._filepath = fileUri[0].fsPath; 97 | this.update( 98 | message.projectGroupSelect, 99 | message.projectGroupSelect, 100 | message.templateName, 101 | message.template, 102 | message.project, 103 | message.solution, 104 | message.framework 105 | ); 106 | } 107 | }); 108 | 109 | this._panel.webview.postMessage({ 110 | command: "updateState", 111 | projectGroupSelect: message.projectGroupSelect, 112 | selectedTemplate: message.template, 113 | }); 114 | return; 115 | } 116 | }, 117 | null, 118 | this._disposables 119 | ); 120 | 121 | // Set the Webview initial html content 122 | this.update(); 123 | } 124 | 125 | private static dispose() { 126 | // Clean up our resources 127 | this._panel.dispose(); 128 | 129 | CreateProjectPanel._panel = undefined as any; 130 | 131 | while (this._disposables.length) { 132 | const x = this._disposables.pop(); 133 | if (x) { 134 | x.dispose(); 135 | } 136 | } 137 | } 138 | 139 | private static async projectCreation(message: Message) { 140 | // Adjust filepath from the default or selection 141 | message.filepath = this._filepath; 142 | 143 | // Remove spaces from project and solution names 144 | message.solution = message.solution.replace(/\s+/g, ""); 145 | message.project = message.project.replace(/\s+/g, ""); 146 | 147 | if (fs.existsSync(this._filepath + "\\" + message.solution)) { 148 | vscode.window.showErrorMessage("Solution folder already exist"); 149 | return; 150 | } 151 | 152 | const command = CommandFactory.getCommand(this._terminal, message); 153 | command.execute(); 154 | 155 | // setting the current project framework to define the template namespace to be used 156 | CreateProjectPanel.context.globalState.update("framework", message.framework); 157 | } 158 | 159 | private static async update( 160 | projectGroupName: any = "Select Project Type", 161 | projectGroup: any = "api", 162 | templateName: any = "Select Template", 163 | template: any = "console", 164 | project: any = "", 165 | solution: any = "", 166 | framework: any = "" 167 | ) { 168 | const webview = this._panel.webview; 169 | 170 | // list of sdk's 171 | const sdksResource: vscode.Uri = webview.asWebviewUri( 172 | vscode.Uri.joinPath(this.context.extensionUri, "media", "sdks.txt") 173 | ); 174 | this._sdks = getTargetFrameworks(sdksResource); 175 | 176 | this._panel.webview.html = this.getHtmlForWebview( 177 | webview, 178 | projectGroupName, 179 | projectGroup, 180 | templateName, 181 | template, 182 | project, 183 | solution, 184 | framework 185 | ); 186 | } 187 | 188 | private static getHtmlForWebview( 189 | webview: vscode.Webview, 190 | projectGroupName: any, 191 | projectGroup: any, 192 | templateName: any, 193 | template: any, 194 | project: any, 195 | solution: any, 196 | framework: any 197 | ) { 198 | // main script integration 199 | const scriptUri = webview.asWebviewUri( 200 | vscode.Uri.joinPath(this.context.extensionUri, "media", "main.js") 201 | ); 202 | 203 | // Local path to css styles 204 | const styleResetPath = vscode.Uri.joinPath(this.context.extensionUri, "media", "reset.css"); 205 | const stylesPathMainPath = vscode.Uri.joinPath( 206 | this.context.extensionUri, 207 | "media", 208 | "vscode.css" 209 | ); 210 | 211 | // Uri to load styles into webview 212 | const stylesResetUri = webview.asWebviewUri(styleResetPath); 213 | const stylesMainUri = webview.asWebviewUri(stylesPathMainPath); 214 | 215 | // Use a nonce to only allow specific scripts to be run 216 | const nonce = getNonce(); 217 | 218 | // Post message transformation before sending to the webview 219 | const frameworkPostMessage = this._sdks 220 | .map((sdk: string) => { 221 | return ``; 224 | }) 225 | .join(""); 226 | 227 | return ` 228 | 229 | 230 | 231 | 234 | 235 | 236 | 237 | 238 | 239 |

Create a new Solution or Project

240 |
241 |
242 |

Select the project type

243 | 262 |
263 |
264 |

Select the project template

265 | 269 |
270 |
271 | 272 | 273 |
274 | 275 |
276 | 279 | 280 |
281 |
282 | 283 | 284 |
285 | 286 |
287 | 290 | 291 | 292 | 293 | `; 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /snippets/general.json: -------------------------------------------------------------------------------- 1 | { 2 | "-> TODO Comments": { 3 | "prefix": "todo", 4 | "body": ["// TODO: ${0: comments}"], 5 | "description": "TODO Comments" 6 | }, 7 | "-> REVIEW Comments": { 8 | "prefix": "review", 9 | "body": ["// REVIEW: ${0: comments}"], 10 | "description": "To Review Comments" 11 | }, 12 | "-> BUG Comments": { 13 | "prefix": "bug", 14 | "body": ["// BUG: ${0: comments}"], 15 | "description": "Bug Comments" 16 | }, 17 | "-> RESEARCH Comments": { 18 | "prefix": "research", 19 | "body": ["// RESEARCH: ${0: comments}"], 20 | "description": "Research Comments" 21 | }, 22 | "-> Console WriteLine": { 23 | "prefix": "cw", 24 | "body": ["Console.${1|WriteLine,Write|}($0);"], 25 | "description": "Console WriteLine();" 26 | }, 27 | "-> WriteLine Interpolation": { 28 | "prefix": "cwi", 29 | "body": ["Console.${1|WriteLine,Write|}($\"${0:Text}\");"], 30 | "description": "Console WriteLine with Interpolation" 31 | }, 32 | "-> Console ReadLine": { 33 | "prefix": "cr", 34 | "body": ["Console.ReadLine();", "$0"], 35 | "description": "Console ReadLine" 36 | }, 37 | "-> Console ReadKey": { 38 | "prefix": "crk", 39 | "body": ["Console.ReadKey(true);", "$0"], 40 | "description": "Console ReadKey" 41 | }, 42 | "-> Console Clear": { 43 | "prefix": "clr", 44 | "body": ["Console.Clear();", "$0"], 45 | "description": "Console Clear" 46 | }, 47 | "-> Variable Declaration": { 48 | "prefix": "var", 49 | "body": ["${1:int} ${2:variable} = ${3:0};", "$0"], 50 | "description": "Variable declaration" 51 | }, 52 | "-> if statement": { 53 | "prefix": "if", 54 | "body": ["if (${1:condition})", "{", "\t$0", "}"], 55 | "description": "Creates an if statement" 56 | }, 57 | "-> Else statement": { 58 | "prefix": "else", 59 | "body": ["else", "{", " $0", "}"], 60 | "description": "Else statement" 61 | }, 62 | "-> if else statement": { 63 | "prefix": "ifelse", 64 | "body": ["if (${1:condition})", "{", "\t$0", "}", "else", "{", "\t", "}"], 65 | "description": "Creates an if else statement" 66 | }, 67 | "-> Conditional operator": { 68 | "prefix": "iif", 69 | "body": [ 70 | "var ${1:variable} = ${2:true};", 71 | "var ${3:result} = (${1} ? ${4:true} : ${5:false});", 72 | "$0" 73 | ], 74 | "description": "Creates a conditional operator" 75 | }, 76 | "-> Enum": { 77 | "prefix": "enum", 78 | "body": ["enum ${1:Name}", "{", " $0", "}"], 79 | "description": "Create a Enum Type" 80 | }, 81 | "-> Switch statement": { 82 | "prefix": "switch", 83 | "body": ["switch (${1:condition})", "{", " $0", " default:", " break;", "}"], 84 | "description": "Create a Switch statement" 85 | }, 86 | "-> Using statement": { 87 | "prefix": "using", 88 | "body": ["using (${1:resource})", "{", "\t$0", "}"], 89 | "description": "Using statement" 90 | }, 91 | "-> While loop": { 92 | "prefix": "while", 93 | "body": ["while (${1:condition})", "{", "\t$0", "}"], 94 | "description": "While loop" 95 | }, 96 | "-> Do while loop": { 97 | "prefix": "dowhile", 98 | "body": ["do", "{", "\t$0", "} while (${1:condition})"], 99 | "description": "Creates a do while loop" 100 | }, 101 | "-> for loop": { 102 | "prefix": "for", 103 | "body": ["for (var ${1:i} = ${2:0}; $1 < ${3:length}; $1++)", "{", "\t$0", "}"], 104 | "description": "Creates a for loop" 105 | }, 106 | "-> reverse for loop": { 107 | "prefix": "forr", 108 | "body": ["for (int ${1:i} = ${2:length}; ${1:i} >= ${3:0} ; ${1:i}--)", "{", "\t$0", "}"], 109 | "description": "Creates a reverse for loop" 110 | }, 111 | "-> foreach statement": { 112 | "prefix": "foreach", 113 | "body": ["foreach (var ${1:item} in ${2:collection})", "{", "\t$0", "}"], 114 | "description": "Creates a foreach statement" 115 | }, 116 | "-> Array": { 117 | "prefix": "arr", 118 | "body": ["${1:type}[] ${2:arrayName} = new ${1}[${3:size}];", "$0"], 119 | "description": "Creates an array" 120 | }, 121 | "-> Var Array": { 122 | "prefix": "varr", 123 | "body": ["var ${1:arrayName} = new ${2:type}[${3:size}];", "$0"], 124 | "description": "Creates an array using var" 125 | }, 126 | "-> List": { 127 | "prefix": "lst", 128 | "body": ["List<${1:type}> ${2:arrayName} = new List<${1}>();", "$0"], 129 | "description": "Creates a list" 130 | }, 131 | "-> Var List": { 132 | "prefix": "vlst", 133 | "body": ["var ${1:arrayName} = new List<${2}>();", "$0"], 134 | "description": "Creates a list with var" 135 | }, 136 | "-> IList": { 137 | "prefix": "ilst", 138 | "body": ["IList<${1:type}> ${2:arrayName} = new List<${1}>();", "$0"], 139 | "description": "Creates a generic list" 140 | }, 141 | "-> Dictionary": { 142 | "prefix": "dic", 143 | "body": [ 144 | "Dictionary<${1:key}, ${2:value}> ${3:dictionaryName} = new Dictionary<${1},${2}>();", 145 | "$0" 146 | ], 147 | "description": "Creates a dictionary" 148 | }, 149 | "-> Var Dictionary": { 150 | "prefix": "vdic", 151 | "body": ["var ${1:dictionaryName} = new Dictionary<${2},${3}>();", "$0"], 152 | "description": "Creates a dictionary with var" 153 | }, 154 | "-> Concurrent Dictionary": { 155 | "prefix": "cdic", 156 | "body": [ 157 | "ConcurrentDictionary<${1:key}, ${2:value}> ${3:dictionaryName} = new ConcurrentDictionary<${1},${2}>();", 158 | "$0" 159 | ], 160 | "description": "Creates a concurrent dictionary" 161 | }, 162 | "-> IDictionary": { 163 | "prefix": "idic", 164 | "body": [ 165 | "IDictionary<${1:key}, ${2:value}> ${3:dictionaryName} = new Dictionary<${1},${2}>();", 166 | "$0" 167 | ], 168 | "description": "Creates a idictionary" 169 | }, 170 | "-> Function": { 171 | "prefix": "func", 172 | "body": ["public ${1:void} ${2:functionName}()", "{", "\t$0", "}"], 173 | "description": "Creates a standard function" 174 | }, 175 | "-> Virtual Function": { 176 | "prefix": "vfunc", 177 | "body": ["public virtual ${1:void} ${2:functionName}()", "{", "\t$0", "}"], 178 | "description": "Creates a virtual function" 179 | }, 180 | "-> Abstract Function": { 181 | "prefix": "afunc", 182 | "body": ["public abstract ${1:void} ${2:functionName}();", "\t$0"], 183 | "description": "Creates a virtual function" 184 | }, 185 | "-> Return Function": { 186 | "prefix": "rfunc", 187 | "body": ["public ${1:int} ${2:functionName}()", "{", "\t$0", "\treturn 0;", "}"], 188 | "description": "Creates a function with return type" 189 | }, 190 | "-> Static Function": { 191 | "prefix": "sfunc", 192 | "body": ["public static ${1:void} ${2:functionName}()", "{", "\t$0", "}"], 193 | "description": "Creates a static function" 194 | }, 195 | "-> Params Function": { 196 | "prefix": "pfunc", 197 | "body": ["public ${1:void} ${2:functionName}(params ${3:type}[] list)", "{", "\t$0", "}"], 198 | "description": "Creates a static function" 199 | }, 200 | "-> Exception Try Catch": { 201 | "prefix": "try", 202 | "body": ["try", "{", "\t$0", "}", "catch (${1:Exception} ${2:ex})", "{", "\t // TODO", "}"], 203 | "description": "Creates a try catch block" 204 | }, 205 | "-> Namespace": { 206 | "prefix": "namespace", 207 | "body": ["namespace ${1:name}", "{", "\t$0", "}"], 208 | "description": "Add namespace based on file directory" 209 | }, 210 | "-> Struct": { 211 | "prefix": "struct", 212 | "body": ["struct ${1:structName}", "{", "\t$0", "}"], 213 | "description": "Creates a struct" 214 | }, 215 | "-> Class": { 216 | "prefix": "class", 217 | "body": ["public class ${TM_FILENAME_BASE}", "{", "\t$0", "}"], 218 | "description": "Creates a basic class" 219 | }, 220 | "-> Class Constructor": { 221 | "prefix": "ctor", 222 | "body": ["public ${TM_FILENAME_BASE}()", "{", "\t$0", "}"], 223 | "description": "Creates a constructor" 224 | }, 225 | "-> Object Instantiation": { 226 | "prefix": "instantiate", 227 | "body": ["${1:class} ${2:objectName} = new ${1}($3);", "$0"], 228 | "description": "Creates an object" 229 | }, 230 | "-> Full Class": { 231 | "prefix": "fclass", 232 | "body": [ 233 | "public class ${TM_FILENAME_BASE}", 234 | "{", 235 | "\tpublic ${TM_FILENAME_BASE}(){}", 236 | "\tpublic override string ToString(){throw new NotImplementedException();}", 237 | "\tpublic override bool Equals(object obj){throw new NotImplementedException();}", 238 | "\tpublic override int GetHashCode(){throw new NotImplementedException();}", 239 | "\t$0", 240 | "}" 241 | ], 242 | "description": "Creates a complete class implementation" 243 | }, 244 | "-> Static Class": { 245 | "prefix": "sclass", 246 | "body": ["public static class ${TM_FILENAME_BASE}", "{", "\t$0", "}"], 247 | "description": "Creates a basic static class" 248 | }, 249 | "-> Abstract Class": { 250 | "prefix": "aclass", 251 | "body": ["public abstract class ${TM_FILENAME_BASE}", "{", "\t$0", "}"], 252 | "description": "Creates an abstract class" 253 | }, 254 | "-> Interface": { 255 | "prefix": "interface", 256 | "body": ["public interface I${TM_FILENAME_BASE}", "{", "\t$0", "}"], 257 | "description": "Creates an interface" 258 | }, 259 | "-> Properties": { 260 | "prefix": "prop", 261 | "body": ["public ${1:type} ${2:Property} { get; set; }"], 262 | "description": "Creates property" 263 | }, 264 | "-> Expanded Properties": { 265 | "prefix": "prope", 266 | "body": [ 267 | "private ${1} _${3:property};", 268 | "public ${1:type} ${2:Property}", 269 | "{", 270 | "\tget => _${3}; ", 271 | "\tset => _${3} = value;", 272 | "}" 273 | ], 274 | "description": "Creates property" 275 | }, 276 | "-> Record": { 277 | "prefix": "record", 278 | "body": ["public record ${1:RecordName}($0);"], 279 | "description": "Creates a record model" 280 | }, 281 | "-> Regex": { 282 | "prefix": "regex", 283 | "body": [ 284 | "// ********** Character Class **********", 285 | "// . any character except newline", 286 | "// \\w\\d\\s word, digit, whitespace.", 287 | "// \\W\\D\\S not word, digit, whitespace", 288 | "// [abc] any of a, b, or c", 289 | "// [^abc] not a, b, or c", 290 | "// [a-g] character between a & g", 291 | "// ********** Anchors **********", 292 | "// ^abc$ start / end of the string", 293 | "// \\b\\B word, not-word boundary", 294 | "// ********** Escaped Characters **********", 295 | "// \\.\\*\\\\ escaped special characters", 296 | "// \\t\\n\\r tab, linefeed, carriage return", 297 | "// ********** Groups & Lookaround **********", 298 | "// (abc) capture group", 299 | "// \\1 backreference to group #1", 300 | "// (?:abc) non-capturing group", 301 | "// (?=abc) positive lookahead,", 302 | "// (?!abc) negative lookahead", 303 | "// ********** Quantifiers & Alternations **********", 304 | "// a*a+a? 0 or more, 1 or more, 0 or 1", 305 | "// a{5}a{2,} exactly five, two or more", 306 | "// a{1,3} between one & three", 307 | "// a+?a{2,}? match as few as possible", 308 | "// ab|cd match ab or cd" 309 | ], 310 | "description": "Regex cheat sheet" 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | C# Snippet Productivity 3 |

4 |

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

19 | 20 | ## Goal 21 | 22 | > - C# Snippet Productivity aims to increase the use of vscode editor as the main tool for console, web and game development in C# Programming Language providing the same shortcuts, efficiency, intellisense that is offered by Visual Studio Community. 23 | > - One of the first objectives is to reduce the amount of extensions downloaded for C# reducing the time and effort on configuration that has to be done by the user as well as to avoid extensions conflicts. Also great features to speed up your development workflow. 24 | 25 | ## Changelog 26 | 27 | > [Click here](https://github.com/rsaz/csharp-snippet-productivity/blob/main/CHANGELOG.md) 28 | 29 | ## What's new in 2.1.1 30 | 31 | > - **_New Feature added_**: Added all current scaffold commands from context menu available in the command palette. 32 | > - **_New Feature added_**: Added template validation against the .NET SDK installed on the machine. 33 | > - **_Fix_**: Adjusted the AddProject command to work with the new template validation and project group selection. 34 | 35 | Observations: 36 | 37 | ```diff 38 | - Not all templates from Project Creation are tested and validated. Please report any issues with the following information to help us to improve the extension: 39 | - - Template Name 40 | - - Template Framework 41 | ``` 42 | 43 | The commands available in the context menu follow a different workflow than the commands available in the command palette. The commands in the context menu will create the project or resource in the same clicked folder. 44 | 45 | The commands in the command palette will ask the user to select the project, create or select the folder, and then create the project. 46 | 47 | Expect a different interaction when using the commands in the context menu and the command palette. 48 | 49 | All commands are available via shortcut keys. You can find the shortcut keys in the command palette. 50 | 51 | - `Ctrl + alt + /` + `p` - Create Project 52 | - `Ctrl + alt + /` + `c` - Create Class 53 | - `Ctrl + alt + /` + `i` - Create Interface 54 | - `Ctrl + alt + /` + `r` - Create Record 55 | - `Ctrl + alt + /` + `s` - Create Struct 56 | - `Ctrl + alt + /` + `a` - Add Project to Solution 57 | 58 | ## What's new in 2.0.1 59 | 60 | > - **_Fix_**: Fixed issues related to design patterns snippets. Added a more modern code approach to the snippets. 61 | 62 | ## What's new in 2.0.0 63 | 64 | > - **_All Project Types_**: Added support for all project types and templates under project creation. 65 | > - **_Support for .NET 7.0 and .NET 8.0_** 66 | > - **_Performance improvements_**: Extension loading time decreased and command execution time decreased. 67 | > - **_Snippet improvements_**: Fixed snippet conflicts and non standard snippets. 68 | > - **_Project Template and Framework Compatibility Validation_**: Validates the project template and framework compatibility based on the .NET SDK installed on the machine. 69 | > - **_Add or Create Project with empty space_**: Added validation to avoid creating projects with empty spaces. 70 | > - **_Suggests the user to add the default folder_**: Reinforce the use of the default folder for project creation. 71 | 72 | ## What's new in 1.3.0 73 | 74 | > - **_New Feature added_**: Minimal Web API, MStest, xUnit, NUnit project template added. 75 | > - **_Fix_**: Creating Solution with the same name in the same directory. 76 | > - **_Improvement_**: Extension loading time decreased. 77 | 78 | ## What's new in 1.2.9 79 | 80 | > - **_New Feature added_**: Scoped namespaces in the .NET 6.0 81 | > - **_Improvement_**: Project creation highlighting the `create project button` after the project name is typed and tab is pressed. 82 | 83 | ## What's new in 1.2.8 84 | 85 | > - **_New Feature added_**: Project support for C# .NET Core 6.0 86 | 87 | ## Current features 88 | 89 | > - **_Fix_**: Classes, Interfaces, and other types created correctly even when the user type incorrect names. 90 | > - **_New Features added_**: Added a default folder for project creation. Add this configuration to your settings with your path: `"csharp-snippet-productivity.defaultFolderForProjectCreation": "D:\\"` **{Your path}** 91 | > - **_New Features added_**: 92 | > - **_Add Project to a Solution_** : Capability to add projects to the same solution with a click of a button. You can select a different project framework as well as the template. 93 | > 94 | > ![Add Project](https://github.com/rsaz/csharp-snippet-productivity/blob/main/videos/addProject.PNG?raw=true) 95 | > 96 | > - **_Submenu With Options_** : 97 | > - Create Class 98 | > - Create Interface 99 | > - Create Record 100 | > - Create Struct 101 | > - **_Fix_**: .NET target frameworks list on project creation are based on OS and SDKs installed. 102 | > - **_Enhancement_**: Design patterns snippets added. It will create a commented pattern code to be used as reference 103 | > - **_singleton_** : Creational singleton pattern 104 | > - **_factoryMethod_** : Creational factory method pattern 105 | > - **_adapter_** : Structural adapter pattern 106 | > - **_observer_**: Structural observer pattern 107 | > - **_Enhancement_**: Regex snippet cheat sheet added. 108 | > - **_regex_** : Regex cheat sheet 109 | > - When creating classes or interfaces system will consider if you have a ``YourUniqueNamespace`` tag on your **_.csproj_**. If the tag is not found system will use your project name as your root namespace 110 | > - Added command to create Class from the context/menu 111 | > - Added command to create Interface from the context/menu 112 | > - How to use: 113 | > - Right click in the project folder or any folder inside of your project folder and select either Create Class or Create Interface 114 | > - Give it a name of your file and class or interface will be created automatically in the selected folder 115 | > 116 | > ![Class and Interface](https://github.com/rsaz/csharp-snippet-productivity/blob/main/videos/classInterface.PNG?raw=true) 117 | 118 | ### Command to Create Solution or Project 119 | 120 | > Command to create projects 121 | > 122 | > Press CTRL + SHIFT + P: Then type: Create Project 123 | > [ C# Toolbox: Create Project ] 124 | > 125 | > > ![Create Project](https://github.com/rsaz/csharp-snippet-productivity/blob/main/videos/createproject.PNG?raw=true) 126 | > 127 | > - Projects templates supported: 128 | > - Blazor Server App 129 | > - Blazor WebAssembly App 130 | > - Console Application 131 | > - Class Library 132 | > - .NET Core: Empty, MVC, Razor Page, Angular SPA, React SPA, React/Redux SPA, Web Api, GRPC Services, Razor Class Library 133 | > 134 | > - Added snippets for creating arrays, lists and dictionaries using var 135 | > - var myArray = new type[size]; 136 | > - var myList = new List\(); 137 | > - var myDictionary = new Dictionary\(); 138 | 139 | > ### Smart Comments 140 | > 141 | > - Colorful and configurable comments to better emphasize your work 142 | > - Snippets: 143 | > - **_todo_** : comments 144 | > - **_review_** : comments 145 | > - **_bug_** : comments 146 | > - **_research_** : comments 147 | > > ![Colored comments](https://github.com/rsaz/csharp-snippet-productivity/blob/main/videos/comment.gif?raw=true) 148 | 149 | > ### General Snippets 150 | > 151 | > - **_cw_** : console write/writeline 152 | > - **_cwi_** : console writeline interpolation 153 | > - **_cr_** : console readline 154 | > - **_crk_**: console readkey 155 | > - **_clr_**: console clear 156 | > - **_var_**: variable declaration 157 | > - **_if_**: if statement 158 | > - **_else_**: else statement 159 | > - **_ifelse_**: if/else statement 160 | > - **_iif_**: conditional operator 161 | > - **_enum_**: enum type 162 | > - **_switch_**: switch statement 163 | > - **_using_**: using statement 164 | > - **_while_**: while loop 165 | > - **_dowhile_**: do/while loop 166 | > - **_for_**: for loop 167 | > - **_foreach_**: foreach loop 168 | > - **_arr_**: array structure 169 | > - **_varr_**: array structure using var 170 | > - **_lst_**: list structure 171 | > - **_vlst_**: list structure using var 172 | > - **_ilst_**: Ilist structure 173 | > - **_dic_**: dictionary structure 174 | > - **_vdic_**: dictionary structure using var 175 | > - **_cdic_**: concurrent dictionary structure 176 | > - **_idic_**: idictionary structure 177 | > - **_func_**: create a void function 178 | > - **_vfunc_**: create a virtual function 179 | > - **_afunc_**: create an abstract function 180 | > - **_rfunc_**: create a function with return type 181 | > - **_sfunc_**: create a static function 182 | > - **_pfunc_**: create a function using params 183 | > - **_try_**: create a try/catch block 184 | > - **_namespace_**: add namespace 185 | > - **_struct_**: create a struct 186 | > - **_class_**: create a class based on the file name 187 | > - **_ctor_**: class constructor 188 | > - **_instantiate_**: object instantiation 189 | > - **_fclass_**: class created with a default constructor and three overrides [ToString, Equals, GetHashCode] 190 | > - **_sclass_**: create a static class 191 | > - **_aclass_**: create an abstract class 192 | > - **_interface_**: create an interface based on the file name 193 | > - **_prop_**: create a property 194 | > - **_prope_**: create an expanded property 195 | > - **_record_**: create a record 196 | > 197 | > ### XML Documentation Snippets 198 | > 199 | > - **_xml-summary_**: this tag adds brief information about a type or member 200 | > - **_xml-remarks_**: the [remarks] tag supplements the information about types or members that the [summary] tag provides 201 | > - **_xml-returns_**: the [returns] tag describes the return value of a method declaration 202 | > - **_xml-value_**: the [value] tag is similar to the [returns] tag, except that you use it for properties 203 | > - **_xml-example_**: You use the [example] tag to include an example in your XML documentation. This involves using the child [code] tag 204 | > - **_xml-para_**: you use the [para] tag to format the content within its parent tag. [para] is usually used inside a tag, such as [remarks] or [returns], to divide text into paragraphs. You can format the contents of the [remarks] tag for your class definition 205 | > - **_xml-c_**: still on the topic of formatting, you use the [c] tag for marking part of text as code. It's like the [code] tag but inline. It's useful when you want to show a quick code example as part of a tag's content 206 | > - **_xml-exception_**: by using the [exception] tag, you let your developers know that a method can throw specific exceptions 207 | > - **_xml-see_**: the [see] tag lets you create a clickable link to a documentation page for another code element 208 | > - **_xml-seealso_**: you use the [seealso] tag in the same way you do the [see] tag. The only difference is that its content is typically placed in a \"See Also\" section 209 | > - **_xml-param_**: you use the [param] tag to describe a method's parameters 210 | > - **_xml-typeparam_**: You use [typeparam] tag just like the [param] tag but for generic type or method declarations to describe a generic parameter 211 | > - **_xml-paramref_**: sometimes you might be in the middle of describing what a method does in what could be a [summary] tag, and you might want to make a reference to a parameter 212 | > - **_xml-typeparamref_**: you use [typeparamref] tag just like the [paramref] tag but for generic type or method declarations to describe a generic parameter 213 | > - **_xml-list_**: you use the [list] tag to format documentation information as an ordered list, unordered list, or table 214 | > - **_xml-inheritdoc_**: you can use the [inheritdoc] tag to inherit XML comments from base classes, interfaces, and similar methods 215 | > - **_xml-include_**: the [include] tag lets you refer to comments in a separate XML file that describe the types and members in your source code, as opposed to placing documentation comments directly in your source code file 216 | 217 | ## How to use 218 | 219 | > - All the snippets comments are shown as -> snippet name 220 | > - Snippets were created thinking on productivity and the extensive use of tab key 221 | > 222 | > ![Add var, class, function](https://github.com/rsaz/csharp-snippet-productivity/blob/main/videos/clipvarclassfunc.gif?raw=true) 223 | > 224 | > ![Add property, dictionary](https://github.com/rsaz/csharp-snippet-productivity/blob/main/videos/clippropdic.gif?raw=true) 225 | > 226 | > - Colored comments were created to increase visibility of todo's, reviews, bugs and research 227 | > 228 | > ![Add list, comments](https://github.com/rsaz/csharp-snippet-productivity/blob/main/videos/cliplistcom.gif?raw=true) 229 | 230 | ## Do you want to contribute? 231 | 232 | ### Guidelines 233 | 234 | > 1. **Fork** the original repository to your own repository 235 | > 2. **Clone** it to your local 236 | > 3. **Contribute to it** 237 | > 4. **Push** it to your remote repo 238 | > 5. Send a **PR** `[Pull Request]` to the main repo 239 | > 6. Your contribution will be evaluated then we will merge your changes with the original repository. ❤ 240 | 241 | ### For more information 242 | 243 | - [Richard Zampieri](https://github.com/rsaz) 244 | 245 | **Enjoy!** 246 | -------------------------------------------------------------------------------- /src/resource/smartComments/Parser.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | interface CommentTag { 4 | tag: string; 5 | escapedTag: string; 6 | decoration: vscode.TextEditorDecorationType; 7 | ranges: Array; 8 | } 9 | 10 | interface Contributions { 11 | multilineComments: boolean; 12 | useJSDocStyle: boolean; 13 | highlightPlainText: boolean; 14 | tags: [{ 15 | tag: string; 16 | color: string; 17 | strikethrough: boolean; 18 | underline: boolean; 19 | bold: boolean; 20 | italic: boolean; 21 | backgroundColor: string; 22 | }]; 23 | } 24 | 25 | export class Parser { 26 | private tags: CommentTag[] = []; 27 | private expression: string = ""; 28 | 29 | private delimiter: string = ""; 30 | private blockCommentStart: string = ""; 31 | private blockCommentEnd: string = ""; 32 | 33 | private highlightSingleLineComments = true; 34 | private highlightMultilineComments = false; 35 | private highlightJSDoc = false; 36 | 37 | // this will allow plaintext files to show comment highlighting if switched on 38 | private isPlainText = false; 39 | 40 | // this is used to prevent the first line of the file (specifically python) from coloring like other comments 41 | private ignoreFirstLine = false; 42 | 43 | // this is used to trigger the events when a supported language code is found 44 | public supportedLanguage = true; 45 | 46 | // Read from the package.json 47 | private contributions: Contributions = vscode.workspace.getConfiguration('csharp-snippet-productivity') as any; 48 | 49 | public constructor() { 50 | this.setTags(); 51 | } 52 | 53 | /** 54 | * Sets the regex to be used by the matcher based on the config specified in the package.json 55 | * @param languageCode The short code of the current language 56 | * https://code.visualstudio.com/docs/languages/identifiers 57 | */ 58 | // eslint-disable-next-line @typescript-eslint/naming-convention 59 | public SetRegex(languageCode: string) { 60 | this.setDelimiter(languageCode); 61 | 62 | // if the language isn't supported, we don't need to go any further 63 | if (!this.supportedLanguage) { 64 | return; 65 | } 66 | 67 | let characters: Array = []; 68 | for (let commentTag of this.tags) { 69 | characters.push(commentTag.escapedTag); 70 | } 71 | 72 | if (this.isPlainText && this.contributions.highlightPlainText) { 73 | // start by tying the regex to the first character in a line 74 | this.expression = "(^)+([ \\t]*[ \\t]*)"; 75 | } else { 76 | // start by finding the delimiter (//, --, #, ') with optional spaces or tabs 77 | this.expression = "(" + this.delimiter.replace(/\//ig, "\\/") + ")+( |\t)*"; 78 | } 79 | 80 | // Apply all configurable comment start tags 81 | this.expression += "("; 82 | this.expression += characters.join("|"); 83 | this.expression += ")+(.*)"; 84 | } 85 | 86 | /** 87 | * Finds all single line comments delimited by a given delimiter and matching tags specified in package.json 88 | * @param activeEditor The active text editor containing the code document 89 | */ 90 | // eslint-disable-next-line @typescript-eslint/naming-convention 91 | public FindSingleLineComments(activeEditor: vscode.TextEditor): void { 92 | 93 | // If highlight single line comments is off, single line comments are not supported for this language 94 | if (!this.highlightSingleLineComments) {return;} 95 | 96 | let text = activeEditor.document.getText(); 97 | 98 | // if it's plain text, we have to do multiline regex to catch the start of the line with ^ 99 | let regexFlags = (this.isPlainText) ? "igm" : "ig"; 100 | let regEx = new RegExp(this.expression, regexFlags); 101 | 102 | let match: any; 103 | while (match = regEx.exec(text)) { 104 | let startPos = activeEditor.document.positionAt(match.index); 105 | let endPos = activeEditor.document.positionAt(match.index + match[0].length); 106 | let range = { range: new vscode.Range(startPos, endPos) }; 107 | 108 | // Required to ignore the first line of .py files (#61) 109 | if (this.ignoreFirstLine && startPos.line === 0 && startPos.character === 0) { 110 | continue; 111 | } 112 | 113 | // Find which custom delimiter was used in order to add it to the collection 114 | let matchTag = this.tags.find(item => item.tag.toLowerCase() === match[3].toLowerCase()); 115 | 116 | if (matchTag) { 117 | matchTag.ranges.push(range); 118 | } 119 | } 120 | } 121 | 122 | /** 123 | * Finds block comments as indicated by start and end delimiter 124 | * @param activeEditor The active text editor containing the code document 125 | */ 126 | // eslint-disable-next-line @typescript-eslint/naming-convention 127 | public FindBlockComments(activeEditor: vscode.TextEditor): void { 128 | 129 | // If highlight multiline is off in package.json or doesn't apply to his language, return 130 | if (!this.highlightMultilineComments) {return;} 131 | 132 | let text = activeEditor.document.getText(); 133 | 134 | // Build up regex matcher for custom delimiter tags 135 | let characters: Array = []; 136 | for (let commentTag of this.tags) { 137 | characters.push(commentTag.escapedTag); 138 | } 139 | 140 | // Combine custom delimiters and the rest of the comment block matcher 141 | let commentMatchString = "(^)+([ \\t]*[ \\t]*)("; 142 | commentMatchString += characters.join("|"); 143 | commentMatchString += ")([ ]*|[:])+([^*/][^\\r\\n]*)"; 144 | 145 | // Use start and end delimiters to find block comments 146 | let regexString = "(^|[ \\t])("; 147 | regexString += this.blockCommentStart; 148 | regexString += "[\\s])+([\\s\\S]*?)("; 149 | regexString += this.blockCommentEnd; 150 | regexString += ")"; 151 | 152 | let regEx = new RegExp(regexString, "gm"); 153 | let commentRegEx = new RegExp(commentMatchString, "igm"); 154 | 155 | // Find the multiline comment block 156 | let match: any; 157 | while (match = regEx.exec(text)) { 158 | let commentBlock = match[0]; 159 | 160 | // Find the line 161 | let line; 162 | while (line = commentRegEx.exec(commentBlock)) { 163 | let startPos = activeEditor.document.positionAt(match.index + line.index + line[2].length); 164 | let endPos = activeEditor.document.positionAt(match.index + line.index + line[0].length); 165 | let range: vscode.DecorationOptions = { range: new vscode.Range(startPos, endPos) }; 166 | 167 | // Find which custom delimiter was used in order to add it to the collection 168 | let matchString = line[3] as string; 169 | let matchTag = this.tags.find(item => item.tag.toLowerCase() === matchString.toLowerCase()); 170 | 171 | if (matchTag) { 172 | matchTag.ranges.push(range); 173 | } 174 | } 175 | } 176 | } 177 | 178 | /** 179 | * Finds all multiline comments starting with "*" 180 | * @param activeEditor The active text editor containing the code document 181 | */ 182 | // eslint-disable-next-line @typescript-eslint/naming-convention 183 | public FindJSDocComments(activeEditor: vscode.TextEditor): void { 184 | 185 | // If highlight multiline is off in package.json or doesn't apply to his language, return 186 | if (!this.highlightMultilineComments && !this.highlightJSDoc) {return;} 187 | 188 | let text = activeEditor.document.getText(); 189 | 190 | // Build up regex matcher for custom delimiter tags 191 | let characters: Array = []; 192 | for (let commentTag of this.tags) { 193 | characters.push(commentTag.escapedTag); 194 | } 195 | 196 | // Combine custom delimiters and the rest of the comment block matcher 197 | let commentMatchString = "(^)+([ \\t]*\\*[ \\t]*)("; // Highlight after leading * 198 | let regEx = /(^|[ \t])(\/\*\*)+([\s\S]*?)(\*\/)/gm; // Find rows of comments matching pattern /** */ 199 | 200 | commentMatchString += characters.join("|"); 201 | commentMatchString += ")([ ]*|[:])+([^*/][^\\r\\n]*)"; 202 | 203 | let commentRegEx = new RegExp(commentMatchString, "igm"); 204 | 205 | // Find the multiline comment block 206 | let match: any; 207 | while (match = regEx.exec(text)) { 208 | let commentBlock = match[0]; 209 | 210 | // Find the line 211 | let line; 212 | while (line = commentRegEx.exec(commentBlock)) { 213 | let startPos = activeEditor.document.positionAt(match.index + line.index + line[2].length); 214 | let endPos = activeEditor.document.positionAt(match.index + line.index + line[0].length); 215 | let range: vscode.DecorationOptions = { range: new vscode.Range(startPos, endPos) }; 216 | 217 | // Find which custom delimiter was used in order to add it to the collection 218 | let matchString = line[3] as string; 219 | let matchTag = this.tags.find(item => item.tag.toLowerCase() === matchString.toLowerCase()); 220 | 221 | if (matchTag) { 222 | matchTag.ranges.push(range); 223 | } 224 | } 225 | } 226 | } 227 | 228 | /** 229 | * Apply decorations after finding all relevant comments 230 | * @param activeEditor The active text editor containing the code document 231 | */ 232 | // eslint-disable-next-line @typescript-eslint/naming-convention 233 | public ApplyDecorations(activeEditor: vscode.TextEditor): void { 234 | for (let tag of this.tags) { 235 | activeEditor.setDecorations(tag.decoration, tag.ranges); 236 | 237 | // clear the ranges for the next pass 238 | tag.ranges.length = 0; 239 | } 240 | } 241 | 242 | /** 243 | * Sets the comment delimiter [//, #, --, '] of a given language 244 | * @param languageCode The short code of the current language 245 | * https://code.visualstudio.com/docs/languages/identifiers 246 | */ 247 | private setDelimiter(languageCode: string): void { 248 | this.supportedLanguage = true; 249 | this.ignoreFirstLine = false; 250 | this.isPlainText = false; 251 | 252 | switch (languageCode) { 253 | case "asciidoc": 254 | this.setCommentFormat("//", "////", "////"); 255 | break; 256 | 257 | case "apex": 258 | case "javascript": 259 | case "javascriptreact": 260 | case "typescript": 261 | case "typescriptreact": 262 | this.setCommentFormat("//", "/*", "*/"); 263 | this.highlightJSDoc = true; 264 | break; 265 | 266 | case "al": 267 | case "c": 268 | case "cpp": 269 | case "csharp": 270 | case "dart": 271 | case "flax": 272 | case "fsharp": 273 | case "go": 274 | case "groovy": 275 | case "haxe": 276 | case "java": 277 | case "jsonc": 278 | case "kotlin": 279 | case "less": 280 | case "pascal": 281 | case "objectpascal": 282 | case "php": 283 | case "rust": 284 | case "scala": 285 | case "sass": 286 | case "scss": 287 | case "shaderlab": 288 | case "stylus": 289 | case "swift": 290 | case "verilog": 291 | case "vue": 292 | this.setCommentFormat("//", "/*", "*/"); 293 | break; 294 | 295 | case "css": 296 | this.setCommentFormat("/*", "/*", "*/"); 297 | break; 298 | 299 | case "coffeescript": 300 | case "dockerfile": 301 | case "gdscript": 302 | case "graphql": 303 | case "julia": 304 | case "makefile": 305 | case "perl": 306 | case "perl6": 307 | case "puppet": 308 | case "r": 309 | case "ruby": 310 | case "shellscript": 311 | case "tcl": 312 | case "yaml": 313 | this.delimiter = "#"; 314 | break; 315 | 316 | case "tcl": 317 | this.delimiter = "#"; 318 | this.ignoreFirstLine = true; 319 | break; 320 | 321 | case "elixir": 322 | case "python": 323 | this.setCommentFormat("#", '"""', '"""'); 324 | this.ignoreFirstLine = true; 325 | break; 326 | 327 | case "nim": 328 | this.setCommentFormat("#", "#[", "]#"); 329 | break; 330 | 331 | case "powershell": 332 | this.setCommentFormat("#", "<#", "#>"); 333 | break; 334 | 335 | case "ada": 336 | case "hive-sql": 337 | case "pig": 338 | case "plsql": 339 | case "sql": 340 | this.delimiter = "--"; 341 | break; 342 | 343 | case "lua": 344 | this.setCommentFormat("--", "--[[", "]]"); 345 | break; 346 | 347 | case "elm": 348 | case "haskell": 349 | this.setCommentFormat("--", "{-", "-}"); 350 | break; 351 | 352 | case "brightscript": 353 | case "diagram": // ? PlantUML is recognized as Diagram (diagram) 354 | case "vb": 355 | this.delimiter = "'"; 356 | break; 357 | 358 | case "bibtex": 359 | case "erlang": 360 | case "latex": 361 | case "matlab": 362 | this.delimiter = "%"; 363 | break; 364 | 365 | case "clojure": 366 | case "racket": 367 | case "lisp": 368 | this.delimiter = ";"; 369 | break; 370 | 371 | case "terraform": 372 | this.setCommentFormat("#", "/*", "*/"); 373 | break; 374 | 375 | case "COBOL": 376 | this.delimiter = this.escapeRegExp("*>"); 377 | break; 378 | 379 | case "fortran-modern": 380 | this.delimiter = "c"; 381 | break; 382 | 383 | case "SAS": 384 | case "stata": 385 | this.setCommentFormat("*", "/*", "*/"); 386 | break; 387 | 388 | case "html": 389 | case "markdown": 390 | case "xml": 391 | this.setCommentFormat(""); 392 | break; 393 | 394 | case "twig": 395 | this.setCommentFormat("{#", "{#", "#}"); 396 | break; 397 | 398 | case "genstat": 399 | this.setCommentFormat("\\", '"', '"'); 400 | break; 401 | 402 | case "cfml": 403 | this.setCommentFormat(""); 404 | break; 405 | 406 | case "plaintext": 407 | this.isPlainText = true; 408 | 409 | // If highlight plaintext is enabeld, this is a supported language 410 | this.supportedLanguage = this.contributions.highlightPlainText; 411 | break; 412 | 413 | default: 414 | this.supportedLanguage = false; 415 | break; 416 | } 417 | } 418 | 419 | /** 420 | * Sets the highlighting tags up for use by the parser 421 | */ 422 | private setTags(): void { 423 | let items = this.contributions.tags; 424 | for (let item of items) { 425 | let options: vscode.DecorationRenderOptions = { color: item.color, backgroundColor: item.backgroundColor }; 426 | 427 | // ? the textDecoration is initialized to empty so we can concat a preceding space on it 428 | options.textDecoration = ""; 429 | 430 | if (item.strikethrough) { 431 | options.textDecoration += "line-through"; 432 | } 433 | 434 | if (item.underline) { 435 | options.textDecoration += " underline"; 436 | } 437 | 438 | if (item.bold) { 439 | options.fontWeight = "bold"; 440 | } 441 | 442 | if (item.italic) { 443 | options.fontStyle = "italic"; 444 | } 445 | 446 | let escapedSequence = item.tag.replace(/([()[{*+.$^\\|?])/g, '\\$1'); 447 | this.tags.push({ 448 | tag: item.tag, 449 | escapedTag: escapedSequence.replace(/\//gi, "\\/"), // ! hardcoded to escape slashes 450 | ranges: [], 451 | decoration: vscode.window.createTextEditorDecorationType(options) 452 | }); 453 | } 454 | } 455 | 456 | /** 457 | * Escapes a given string for use in a regular expression 458 | * @param input The input string to be escaped 459 | * @returns {string} The escaped string 460 | */ 461 | private escapeRegExp(input: string): string { 462 | return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string 463 | } 464 | 465 | /** 466 | * Set up the comment format for single and multiline highlighting 467 | * @param singleLine The single line comment delimiter. If NULL, single line is not supported 468 | * @param start The start delimiter for block comments 469 | * @param end The end delimiter for block comments 470 | */ 471 | private setCommentFormat(singleLine: string | null, start: string, end: string): void { 472 | 473 | // If no single line comment delimiter is passed, single line comments are not supported 474 | if (singleLine) { 475 | this.delimiter = this.escapeRegExp(singleLine); 476 | } 477 | else { 478 | this.highlightSingleLineComments = false; 479 | } 480 | 481 | this.blockCommentStart = this.escapeRegExp(start); 482 | this.blockCommentEnd = this.escapeRegExp(end); 483 | this.highlightMultilineComments = this.contributions.multilineComments; 484 | } 485 | } --------------------------------------------------------------------------------