├── .gitignore ├── logo.png ├── featureimages ├── newclass.gif ├── ctorfromprop.gif ├── fieldfromctor.gif └── newinterface.gif ├── templates ├── class.tmpl └── interface.tmpl ├── .vscodeignore ├── tsconfig.json ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── test ├── extension.test.ts └── index.ts ├── README.md ├── licence.txt ├── CHANGELOG.md ├── vsc-extension-quickstart.md ├── package.json └── src ├── extension.ts └── codeActionProvider.ts /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | *.vsix -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephwoodward/csharpextensions/master/logo.png -------------------------------------------------------------------------------- /featureimages/newclass.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephwoodward/csharpextensions/master/featureimages/newclass.gif -------------------------------------------------------------------------------- /featureimages/ctorfromprop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephwoodward/csharpextensions/master/featureimages/ctorfromprop.gif -------------------------------------------------------------------------------- /featureimages/fieldfromctor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephwoodward/csharpextensions/master/featureimages/fieldfromctor.gif -------------------------------------------------------------------------------- /featureimages/newinterface.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephwoodward/csharpextensions/master/featureimages/newinterface.gif -------------------------------------------------------------------------------- /templates/class.tmpl: -------------------------------------------------------------------------------- 1 | namespace ${namespace} 2 | { 3 | public class ${classname} 4 | { 5 | ${cursor} 6 | } 7 | } -------------------------------------------------------------------------------- /templates/interface.tmpl: -------------------------------------------------------------------------------- 1 | namespace ${namespace} 2 | { 3 | public interface ${classname} 4 | { 5 | ${cursor} 6 | } 7 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | test/** 5 | src/** 6 | **/*.map 7 | .gitignore 8 | tsconfig.json 9 | vsc-extension-quickstart.md 10 | *.vsix 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ] 16 | } -------------------------------------------------------------------------------- /.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 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version 10 | } -------------------------------------------------------------------------------- /test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as vscode from 'vscode'; 12 | import * as myExtension from '../src/extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", () => { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", () => { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C# Extensions 2 | 3 | Welcome to C# Extensions. This VSCode extension provides extensions to the IDE that will hopefully speed up your development workflow. 4 | 5 | ## Features 6 | 7 | **Add C# Class** 8 | 9 | ![Add C# Class](./featureimages/newclass.gif) 10 | 11 | **Add C# Interface** 12 | 13 | ![Add C# Interface](./featureimages/newinterface.gif) 14 | 15 | **Add fields from constructors** 16 | 17 | ![Add fields from constructors](./featureimages/fieldfromctor.gif) 18 | 19 | **Add constructor from properties** 20 | 21 | ![Add constructor from properties](./featureimages/ctorfromprop.gif) 22 | 23 | 24 | The Add C# Class/Interface works **only** on dotnetcore projects. It traverses up the folder tree to find the project.json and uses that as the parent folder to determine namspaces. 25 | 26 | 27 | ----------------------------------------------------------------------------------------------------------- 28 | 29 | ## Licence 30 | 31 | MIT 32 | 33 | See [licence.txt](./licence.txt) 34 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outDir": "${workspaceRoot}/out/src", 14 | "preLaunchTask": "npm" 15 | }, 16 | { 17 | "name": "Launch Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outDir": "${workspaceRoot}/out/test", 25 | "preLaunchTask": "npm" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "0.1.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "isShellCommand": true, 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isWatching": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } -------------------------------------------------------------------------------- /licence.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016 Jonathan Channon and contributors 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [1.1.0] - 2016-11-25 5 | ### Added 6 | - Initialize fields from constructor 7 | - Initialize constructor from properties 8 | 9 | ## [1.0.9] - 2016-11-03 10 | ###Changed 11 | - Bug fix for paths with spaces in them, creates namespace with underscore insteadnot sure 12 | 13 | ## [1.0.8] - 2016-10-28 14 | ###Changed 15 | - Bug fix for paths with multiple hyphens 16 | 17 | ## [1.0.7] - 2016-10-18 18 | ### Changed 19 | - Bug fix for extension of new file 20 | 21 | ## [1.0.6] - 2016-10-17 22 | ### Changed 23 | - If no extension exists then it will add .cs on the end 24 | 25 | ## [1.0.5] - 2016-10-17 26 | ### Changed 27 | - Ask only for filename instead of full path 28 | 29 | ## [1.0.4] - 2016-10-17 30 | ### Changed 31 | - If path containes hyphen in path, make sure this becomes an underscore like VS 32 | 33 | ## [1.0.3] - 2016-10-16 34 | ### Changed 35 | - Removed change log from release notes.md 36 | 37 | ## [1.0.2] - 2016-10-16 38 | ### Added 39 | - Works on Windows 40 | 41 | ## [1.0.1] - 2016-10-14 42 | ### Added 43 | - Can create class from root folder 44 | - Templates can specifiy where cursor exists 45 | 46 | ## [1.0.0] - 2016-10-14 47 | ### Added 48 | - Intial Release -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your first VS Code Extension 2 | 3 | ## What's in the folder 4 | * This folder contains all of the files necessary for your extension 5 | * `package.json` - this is the manifest file in which you declare your extension and command. 6 | The sample plugin registers a command and defines its title and command name. With this information 7 | VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | The file exports one function, `activate`, which is called the very first time your extension is 10 | activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 11 | We pass the function containing the implementation of the command as the second parameter to 12 | `registerCommand`. 13 | 14 | ## Get up and running straight away 15 | * press `F5` to open a new window with your extension loaded 16 | * run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World` 17 | * set breakpoints in your code inside `src/extension.ts` to debug your extension 18 | * find output from your extension in the debug console 19 | 20 | ## Make changes 21 | * you can relaunch the extension from the debug toolbar after changing code in `src/extension.ts` 22 | * you can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes 23 | 24 | ## Explore the API 25 | * you can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts` 26 | 27 | ## Run tests 28 | * open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests` 29 | * press `F5` to run the tests in a new window with your extension loaded 30 | * see the output of the test result in the debug console 31 | * make changes to `test/extension.test.ts` or create new test files inside the `test` folder 32 | * by convention, the test runner will only consider files matching the name pattern `**.test.ts` 33 | * you can create folders inside the `test` folder to structure your tests any way you want -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csharpextensions", 3 | "displayName": "C# Extensions", 4 | "description": "C# IDE Extensions for VSCode", 5 | "author": "Jonathan Channon", 6 | "license": "SEE LICENSE IN license.txt", 7 | "version": "1.1.0", 8 | "publisher": "jchannon", 9 | "engines": { 10 | "vscode": "^1.5.0" 11 | }, 12 | "repository": "https://github.com/jchannon/csharpextensions", 13 | "icon": "logo.png", 14 | "categories": [ 15 | "Languages", 16 | "Linters", 17 | "Snippets" 18 | ], 19 | "activationEvents": [ 20 | "onLanguage:csharp", 21 | "onCommand:csharpextensions.createClass", 22 | "onCommand:csharpextensions.createInterface" 23 | ], 24 | "main": "./out/src/extension", 25 | "contributes": { 26 | "commands": [ 27 | { 28 | "command": "csharpextensions.createClass", 29 | "title": "New C# Class" 30 | }, 31 | { 32 | "command": "csharpextensions.createInterface", 33 | "title": "New C# Interface" 34 | } 35 | ], 36 | "menus": { 37 | "explorer/context": [ 38 | { 39 | "group": "navigation@-1", 40 | "command": "csharpextensions.createClass" 41 | }, 42 | { 43 | "group": "navigation@-1", 44 | "command": "csharpextensions.createInterface" 45 | } 46 | ] 47 | }, 48 | "configuration": { 49 | "title": "C# Extensions configuration", 50 | "properties": { 51 | "csharpextensions.privateMemberPrefix": { 52 | "type": "string", 53 | "default": "", 54 | "description": "Prefix for generated private member declarations" 55 | }, 56 | "csharpextensions.reFormatAfterChange": { 57 | "type": "boolean", 58 | "default": true, 59 | "description": "If true the document will be reformatted after codeactions are used." 60 | } 61 | } 62 | } 63 | }, 64 | "scripts": { 65 | "vscode:prepublish": "node_modules/typescript/bin/tsc -p ./", 66 | "compile": "node_modules/typescript/bin/tsc -watch -p ./", 67 | "postinstall": "node ./node_modules/vscode/bin/install" 68 | }, 69 | "devDependencies": { 70 | "typescript": "^2.0.3", 71 | "vscode": "^1.0.0", 72 | "mocha": "^2.3.3", 73 | "@types/node": "^6.0.40", 74 | "@types/mocha": "^2.2.32" 75 | }, 76 | "dependencies": { 77 | "find-parent-dir": "^0.3.0" 78 | } 79 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // The module 'vscode' contains the VS Code extensibility API 3 | // Import the module and reference it with the alias vscode in your code below 4 | import * as vscode from 'vscode'; 5 | import * as path from 'path'; 6 | import * as fs from 'fs'; 7 | import * as os from 'os'; 8 | import CodeActionProvider from './codeActionProvider'; 9 | var parentfinder = require('find-parent-dir'); 10 | 11 | // this method is called when your extension is activated 12 | // your extension is activated the very first time the command is executed 13 | export function activate(context: vscode.ExtensionContext) { 14 | const documentSelector: vscode.DocumentSelector = { 15 | language: 'csharp', 16 | scheme: 'file' 17 | }; 18 | // Use the console to output diagnostic information (console.log) and errors (console.error) 19 | // This line of code will only be executed once when your extension is activated 20 | //console.log('Congratulations, your extension "newclassextension" is now active!'); 21 | 22 | // The command has been defined in the package.json file 23 | // Now provide the implementation of the command with registerCommand 24 | // The commandId parameter must match the command field in package.json 25 | // let disposable = vscode.commands.registerCommand('extension.sayHello', (args) => { 26 | // // The code you place here will be executed every time your command is executed 27 | // // Display a message box to the user 28 | // vscode.window.showInformationMessage('Hello World!'); 29 | // }); 30 | 31 | //context.subscriptions.push(disposable); 32 | context.subscriptions.push(vscode.commands.registerCommand('csharpextensions.createClass', createClass)); 33 | context.subscriptions.push(vscode.commands.registerCommand('csharpextensions.createInterface', createInterface)); 34 | 35 | const codeActionProvider = new CodeActionProvider(); 36 | let disposable = vscode.languages.registerCodeActionsProvider(documentSelector, codeActionProvider); 37 | context.subscriptions.push(disposable); 38 | } 39 | 40 | function createClass(args) { 41 | promptAndSave(args, 'class'); 42 | } 43 | 44 | function createInterface(args) { 45 | promptAndSave(args, 'interface'); 46 | } 47 | 48 | function promptAndSave(args, templatetype: string) { 49 | if (args == null) { 50 | args = { _fsPath: vscode.workspace.rootPath } 51 | } 52 | let incomingpath: string = args._fsPath; 53 | vscode.window.showInputBox({ ignoreFocusOut: true, prompt: 'Please enter filename', value: 'new' + templatetype + '.cs' }) 54 | .then((filename) => { 55 | 56 | if (typeof filename === 'undefined') { 57 | return; 58 | } 59 | 60 | filename = incomingpath + path.sep + filename; 61 | 62 | if (path.extname(filename) !== '.cs') { 63 | if (filename.endsWith('.')) { 64 | filename = filename + 'cs'; 65 | } else { 66 | filename = filename + '.cs'; 67 | } 68 | } 69 | 70 | var originalfilename = filename; 71 | 72 | var parentdir = parentfinder.sync(path.dirname(filename), 'project.json'); 73 | if (parentdir[parentdir.length - 1] === path.sep) { 74 | parentdir = parentdir.substr(0, parentdir.length - 1); 75 | } 76 | 77 | var newroot = parentdir.substr(parentdir.lastIndexOf(path.sep) + 1); 78 | 79 | var filenamechildpath = filename.substring(filename.lastIndexOf(newroot)); 80 | 81 | var pathSepRegEx = /\//g; 82 | if (os.platform() === "win32") 83 | pathSepRegEx = /\\/g; 84 | 85 | var namespace = path.dirname(filenamechildpath); 86 | namespace = namespace.replace(pathSepRegEx, '.'); 87 | 88 | namespace = namespace.replace(/\s+/g, "_"); 89 | namespace = namespace.replace(/-/g, "_"); 90 | 91 | filename = path.basename(filename, '.cs'); 92 | 93 | openTemplateAndSaveNewFile(templatetype, namespace, filename, originalfilename); 94 | }); 95 | } 96 | 97 | function openTemplateAndSaveNewFile(type: string, namespace: string, filename: string, originalfilename: string) { 98 | 99 | let templatefileName = type + '.tmpl'; 100 | 101 | vscode.workspace.openTextDocument(vscode.extensions.getExtension('jchannon.csharpextensions').extensionPath + '/templates/' + templatefileName) 102 | .then((doc: vscode.TextDocument) => { 103 | let text = doc.getText(); 104 | text = text.replace('${namespace}', namespace); 105 | text = text.replace('${classname}', filename); 106 | let cursorPosition = findCursorInTemlpate(text); 107 | text = text.replace('${cursor}', ''); 108 | fs.writeFileSync(originalfilename, text); 109 | 110 | vscode.workspace.openTextDocument(originalfilename).then((doc) => { 111 | vscode.window.showTextDocument(doc).then((editor) => { 112 | let newselection = new vscode.Selection(cursorPosition, cursorPosition); 113 | editor.selection = newselection; 114 | }); 115 | }); 116 | }); 117 | } 118 | 119 | function findCursorInTemlpate(text: string) { 120 | let cursorPos = text.indexOf('${cursor}'); 121 | let preCursor = text.substr(0, cursorPos); 122 | let lineNum = preCursor.match(/\n/gi).length; 123 | let charNum = preCursor.substr(preCursor.lastIndexOf('\n')).length; 124 | return new vscode.Position(lineNum, charNum); 125 | 126 | } 127 | 128 | // this method is called when your extension is deactivated 129 | export function deactivate() { 130 | } 131 | -------------------------------------------------------------------------------- /src/codeActionProvider.ts: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import * as vscode from 'vscode'; 3 | 4 | export default class CodeActionProvider implements vscode.CodeActionProvider{ 5 | private _commandIds = { 6 | ctorFromProperties: 'csharpextensions.ctorFromProperties', 7 | initializeMemberFromCtor: 'csharpextensions.initializeMemberFromCtor' 8 | }; 9 | 10 | constructor() { 11 | vscode.commands.registerCommand(this._commandIds.initializeMemberFromCtor, this.initializeMemberFromCtor, this); 12 | vscode.commands.registerCommand(this._commandIds.ctorFromProperties, this.executeCtorFromProperties, this); 13 | } 14 | 15 | 16 | public provideCodeActions(document:vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken) : vscode.Command[] { 17 | var commands = []; 18 | 19 | var initalizeFromCtorCommand = this.getInitializeFromCtorCommand(document,range,context,token); 20 | if(initalizeFromCtorCommand) 21 | commands.push(initalizeFromCtorCommand) 22 | 23 | var ctorPCommand = this.getCtorpCommand(document, range, context, token); 24 | if(ctorPCommand) 25 | commands.push(ctorPCommand); 26 | 27 | return commands; 28 | } 29 | 30 | private camelize(str) { 31 | return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) { 32 | if (+match === 0) return ""; // or if (/\s+/.test(match)) for white spaces 33 | return index == 0 ? match.toLowerCase() : match.toUpperCase(); 34 | }); 35 | } 36 | 37 | private executeCtorFromProperties(args:ConstructorFromPropertiesArgument) { 38 | var tabSize = vscode.workspace.getConfiguration().get('editor.tabSize', 4); 39 | let ctorParams = []; 40 | 41 | if(!args.properties) 42 | return; 43 | 44 | args.properties.forEach((p)=>{ 45 | ctorParams.push(`${p.type} ${this.camelize(p.name)}`) 46 | }); 47 | 48 | let assignments = []; 49 | args.properties.forEach((p)=>{ 50 | assignments.push(`${Array(tabSize*1).join(' ')} this.${p.name} = ${this.camelize(p.name)}; 51 | `); 52 | }); 53 | 54 | let firstPropertyLine = args.properties.sort((a,b)=>{ 55 | return a.lineNumber-b.lineNumber 56 | })[0].lineNumber; 57 | 58 | var ctorStatement = `${Array(tabSize*2).join(' ')} ${args.classDefinition.modifier} ${args.classDefinition.className}(${ctorParams.join(', ')}) 59 | { 60 | ${assignments.join('')} 61 | } 62 | `; 63 | 64 | let edit = new vscode.WorkspaceEdit(); 65 | let edits = []; 66 | 67 | let pos = new vscode.Position(firstPropertyLine, 0); 68 | let range = new vscode.Range(pos,pos); 69 | let ctorEdit = new vscode.TextEdit(range, ctorStatement); 70 | 71 | edits.push(ctorEdit) 72 | edit.set(args.document.uri, edits); 73 | 74 | let reFormatAfterChange = vscode.workspace.getConfiguration().get('csharpextensions.reFormatAfterChange', true); 75 | let applyPromise = vscode.workspace.applyEdit(edit) 76 | 77 | if(reFormatAfterChange){ 78 | applyPromise.then(()=>{ 79 | vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', args.document.uri).then((formattingEdits:vscode.TextEdit[])=>{ 80 | var formatEdit = new vscode.WorkspaceEdit(); 81 | formatEdit.set(args.document.uri,formattingEdits); 82 | vscode.workspace.applyEdit(formatEdit); 83 | }); 84 | }) 85 | } 86 | } 87 | 88 | private getCtorpCommand(document:vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken) : vscode.Command { 89 | const editor = vscode.window.activeTextEditor; 90 | const position = editor.selection.active; 91 | 92 | let withinClass = this.findClassFromLine(document, position.line); 93 | if(!withinClass) 94 | return null; 95 | 96 | let properties = []; 97 | let lineNo = 0; 98 | 99 | while(lineNo < document.lineCount) { 100 | let readonlyRegex = new RegExp(/(public|private|protected)\s(\w+)\s(\w+)\s?{\s?(get;)\s?(private\s)?(set;)?\s?}/g); 101 | let textLine = document.lineAt(lineNo); 102 | let match = readonlyRegex.exec(textLine.text); 103 | 104 | if(match){ 105 | let clazz = this.findClassFromLine(document,lineNo); 106 | if(clazz.className === withinClass.className) { 107 | let prop: CSharpPropertyDefinition = { 108 | lineNumber: lineNo, 109 | class: clazz, 110 | modifier: match[1], 111 | type: match[2], 112 | name: match[3], 113 | statement: match[0] 114 | } 115 | 116 | properties.push(prop); 117 | } 118 | } 119 | lineNo+=1; 120 | } 121 | 122 | if(!properties.length) 123 | return null; 124 | 125 | var classDefinition = this.findClassFromLine(document, position.line); 126 | if(!classDefinition) 127 | return; 128 | 129 | var parameter : ConstructorFromPropertiesArgument = { 130 | properties: properties, 131 | classDefinition: classDefinition, 132 | document: document 133 | }; 134 | 135 | let cmd: vscode.Command = { 136 | title: "Initialize ctor from properties...", 137 | command: this._commandIds.ctorFromProperties, 138 | arguments: [parameter] 139 | }; 140 | 141 | return cmd; 142 | } 143 | 144 | private findClassFromLine(document:vscode.TextDocument, lineNo:number) : CSharpClassDefinition { 145 | var classRegex = new RegExp(/(private|internal|public|protected)\s?(static)?\sclass\s(\w*)/g); 146 | while(lineNo > 0){ 147 | var line = document.lineAt(lineNo); 148 | let match; 149 | if((match = classRegex.exec(line.text))){ 150 | return { 151 | startLine: lineNo, 152 | endLine: -1, 153 | className: match[3], 154 | modifier: match[1], 155 | statement: match[0] 156 | }; 157 | } 158 | lineNo -=1; 159 | } 160 | return null; 161 | } 162 | 163 | private initializeMemberFromCtor(args:InitializeFieldFromConstructor) { 164 | let edit = new vscode.WorkspaceEdit(); 165 | 166 | var bodyStartRange = new vscode.Range(args.constructorBodyStart, args.constructorBodyStart) 167 | var declarationRange = new vscode.Range(args.constructorStart, args.constructorStart); 168 | 169 | let declarationEdit = new vscode.TextEdit(declarationRange, args.privateDeclaration); 170 | let memberInitEdit = new vscode.TextEdit(bodyStartRange, args.memberInitialization); 171 | 172 | var edits = []; 173 | if(args.document.getText().indexOf(args.privateDeclaration.trim()) == -1){ 174 | edits.push(declarationEdit); 175 | } 176 | 177 | if(args.document.getText().indexOf(args.memberInitialization.trim())== -1){ 178 | edits.push(memberInitEdit); 179 | } 180 | 181 | edit.set(args.document.uri, edits); 182 | 183 | var reFormatAfterChange = vscode.workspace.getConfiguration().get('csharpextensions.reFormatAfterChange', true); 184 | var applyPromise = vscode.workspace.applyEdit(edit); 185 | 186 | if(reFormatAfterChange){ 187 | applyPromise.then(()=>{ 188 | vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', args.document.uri).then((formattingEdits:vscode.TextEdit[])=>{ 189 | var formatEdit = new vscode.WorkspaceEdit(); 190 | formatEdit.set(args.document.uri,formattingEdits); 191 | vscode.workspace.applyEdit(formatEdit); 192 | }); 193 | }) 194 | } 195 | } 196 | 197 | private getInitializeFromCtorCommand(document:vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken):vscode.Command { 198 | const editor = vscode.window.activeTextEditor; 199 | const position = editor.selection.active; 200 | var surrounding = document.getText(new vscode.Range(new vscode.Position(position.line-2,0),new vscode.Position(position.line+2,0))); 201 | let wordRange = editor.document.getWordRangeAtPosition(position); 202 | if(!wordRange) 203 | return null; 204 | 205 | var regex = new RegExp(/(public|private|protected)\s(.*)\(([\s\S]*?)\)/gi); 206 | var matches = regex.exec(surrounding); 207 | if(!matches) 208 | return null; 209 | 210 | var ctorStartPos = editor.document.getText().indexOf(matches[0]); 211 | 212 | var ctorParamStr = matches[3]; 213 | let parameters = {}; 214 | ctorParamStr.split(',').forEach((match)=>{ 215 | var separated = match.trim().split(' '); 216 | var type = separated[0].trim(); 217 | var name = separated[1].trim(); 218 | parameters[name] = type; 219 | }); 220 | 221 | var lineText = editor.document.getText(new vscode.Range(position.line,0,position.line,wordRange.end.character)); 222 | var selectedName = lineText.substr(wordRange.start.character,wordRange.end.character-wordRange.start.character); 223 | var parameterType = parameters[selectedName]; 224 | if(!parameterType){ 225 | return; 226 | } 227 | 228 | var tabSize = vscode.workspace.getConfiguration().get('editor.tabSize', 4); 229 | var privateMemberPrefix = vscode.workspace.getConfiguration().get('csharpextensions.privateMemberPrefix', ''); 230 | 231 | var parameter:InitializeFieldFromConstructor = { 232 | document: document, 233 | type: parameterType, 234 | name: selectedName, 235 | privateDeclaration: `${Array(tabSize*2).join(' ')} private readonly ${parameterType} ${privateMemberPrefix}${selectedName};\r\n`, 236 | memberInitialization: `${Array(tabSize*3).join(' ')} this.${privateMemberPrefix}${selectedName} = ${selectedName};\r\n`, 237 | constructorBodyStart: this.findConstructorBodyStart(document,position), 238 | constructorStart: this.findConstructorStart(document,position) 239 | }; 240 | 241 | let cmd: vscode.Command = { 242 | title: "Initialize field from parameter...", 243 | command: this._commandIds.initializeMemberFromCtor, 244 | arguments: [parameter] 245 | }; 246 | 247 | return cmd; 248 | } 249 | 250 | private findConstructorBodyStart(document:vscode.TextDocument, position:vscode.Position): vscode.Position{ 251 | for (var lineNo = position.line; lineNo < position.line+5; lineNo++) { 252 | var line = document.lineAt(lineNo); 253 | var braceIdx = line.text.indexOf('{'); 254 | if(braceIdx != -1){ 255 | return new vscode.Position(lineNo+1,0); 256 | } 257 | } 258 | return null; 259 | } 260 | 261 | private findConstructorStart(document:vscode.TextDocument, position:vscode.Position): vscode.Position{ 262 | for (var lineNo = position.line; lineNo > position.line-5; lineNo--) { 263 | var line = document.lineAt(lineNo); 264 | if(line.isEmptyOrWhitespace){ 265 | return new vscode.Position(lineNo,0); 266 | } 267 | } 268 | 269 | return new vscode.Position(position.line,0); 270 | } 271 | } 272 | 273 | interface CSharpClassDefinition { 274 | startLine: number, 275 | endLine: number, 276 | className: string, 277 | modifier: string, 278 | statement: string 279 | } 280 | 281 | interface CSharpPropertyDefinition { 282 | class: CSharpClassDefinition, 283 | modifier: string, 284 | type: string, 285 | name: string, 286 | statement: string, 287 | lineNumber: number 288 | } 289 | 290 | interface ConstructorFromPropertiesArgument{ 291 | document: vscode.TextDocument, 292 | classDefinition: CSharpClassDefinition, 293 | properties: CSharpPropertyDefinition[] 294 | } 295 | 296 | interface InitializeFieldFromConstructor { 297 | document: vscode.TextDocument, 298 | type:string, 299 | name: string, 300 | privateDeclaration: string, 301 | memberInitialization:string, 302 | constructorBodyStart: vscode.Position, 303 | constructorStart: vscode.Position, 304 | } --------------------------------------------------------------------------------