Skip to content
This repository was archived by the owner on Sep 24, 2018. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
out
node_modules
*.vsix
*.vsix
package-lock.json
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
# Change Log
All notable changes to this project will be documented in this file.

## [1.3.2] - 2018-01-19
## Added
- Support for compilation roots defined in csproj file. The XML definition of the dotnet project is read and namespace tokens which are included for compilation will be removed.

## [1.3.1] - 2017-12-17
### Added
- Namespace tokens will be capitalized on file creation
- Namespace tokens can be overriden by configuration values.

### Changed
- Fixed namespace generation if project root name is equal to a namespace token.
- Change filename presets on file creation to case sensitive.
- newinterface.cs -> INewInterface.cs
- newclass.cs -> INewClass.cs

## [1.3.0] - 2017-03-01
### Added
- Ability to create property and read-only property from constructor
Expand Down
23 changes: 19 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
]
},
"configuration": {
"title": "C# Extensions configuration",
"title": "C# Extensions",
"properties": {
"csharpextensions.privateMemberPrefix": {
"type": "string",
Expand All @@ -62,6 +62,19 @@
"type": "boolean",
"default": true,
"description": "If true the document will be reformatted after codeactions are used."
},
"csharpextensions.namespace.capitalize": {
"type": "boolean",
"default": true,
"description": "If true the tokens of the generated namespace will be capitalized. For example the path 'my/vscode/project/path' will become 'My.Vscode.Project.Path'."
},
"csharpextensions.namespace.tokenMappings": {
"type": [
"object",
null
],
"default": null,
"description": "An mapping of namepsace tokens which will be replaced by the value. Usefull for namespace tokens which should be written in a different manner like shorthands or special names. For example {'vscode': 'VSCode'} will replace the 'vscode' token by VSCode in namespace declaration."
}
}
}
Expand All @@ -76,10 +89,12 @@
"vscode": "^1.0.0",
"mocha": "^2.3.3",
"@types/node": "^6.0.40",
"@types/mocha": "^2.2.32"
"@types/mocha": "^2.2.32",
"@types/xml2js": "^0.4.2"
},
"dependencies": {
"find-parent-dir": "^0.3.0",
"find-up-glob": "^1.0.0"
"find-up-glob": "^1.0.0",
"xml2js": "^0.4.19"
}
}
}
125 changes: 94 additions & 31 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
import * as xml2js from 'xml2js';
import CodeActionProvider from './codeActionProvider';
const parentfinder = require('find-parent-dir');
const findupglob = require('find-up-glob');
Expand All @@ -16,6 +16,7 @@ export function activate(context: vscode.ExtensionContext) {
language: 'csharp',
scheme: 'file'
};

// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
//console.log('Congratulations, your extension "newclassextension" is now active!');
Expand Down Expand Up @@ -51,14 +52,16 @@ function promptAndSave(args, templatetype: string) {
args = { _fsPath: vscode.workspace.rootPath }
}
let incomingpath: string = args._fsPath;
vscode.window.showInputBox({ ignoreFocusOut: true, prompt: 'Please enter filename', value: 'new' + templatetype + '.cs' })

let promptPrefix = templatetype === 'interface' ? 'INew' : 'New';
vscode.window.showInputBox({ ignoreFocusOut: true, prompt: 'Please enter filename', value: promptPrefix + capitalize(templatetype) + '.cs'})
.then((newfilename) => {

if (typeof newfilename === 'undefined') {
return;
}

var newfilepath = incomingpath + path.sep + newfilename;
let newfilepath = incomingpath + path.sep + newfilename;

if (fs.existsSync(newfilepath)) {
vscode.window.showErrorMessage("File already exists");
Expand All @@ -67,37 +70,53 @@ function promptAndSave(args, templatetype: string) {

newfilepath = correctExtension(newfilepath);

var originalfilepath = newfilepath;
let originalfilepath = newfilepath;

var projectrootdir = getProjectRootDirOfFilePath(newfilepath);
let projectFile = getProjectFile(newfilepath);

if (projectrootdir == null) {
if (projectFile == null) {
vscode.window.showErrorMessage("Unable to find project.json or *.csproj");
return;
}

projectrootdir = removeTrailingSeparator(projectrootdir);

var newroot = projectrootdir.substr(projectrootdir.lastIndexOf(path.sep) + 1);
let projectRootFolder = path.dirname(projectFile);
let filenamechildpath = newfilepath.slice(projectRootFolder.length);
if(filenamechildpath.startsWith(path.sep)) {
filenamechildpath = filenamechildpath.substring(1);
}

var filenamechildpath = newfilepath.substring(newfilepath.lastIndexOf(newroot));
let namespaceTokens = path.dirname(filenamechildpath)
.split(path.sep)
.filter(token => token.trim.length <= 0);

var pathSepRegEx = /\//g;
if (os.platform() === "win32")
pathSepRegEx = /\\/g;
if(projectFile.endsWith(".csproj")) {
namespaceTokens = removeCompilationRootFolder(projectFile, namespaceTokens);
}

var namespace = path.dirname(filenamechildpath);
namespace = namespace.replace(pathSepRegEx, '.');
if(vscode.workspace.getConfiguration().get('csharpextensions.namespace.capitalize')) {
namespaceTokens = namespaceTokens.map(item => capitalize(item));
}
let namespaceTokenMappings = vscode.workspace.getConfiguration().get('csharpextensions.namespace.tokenMappings');
if(namespaceTokenMappings instanceof Object) {
namespaceTokens = namespaceTokens.map(item =>
namespaceTokenMappings[item.toLowerCase()] != null ?
namespaceTokenMappings[item.toLowerCase()] : item);
}

let namespace = namespaceTokens.join('.');
namespace = namespace.replace(/\s+/g, "_");
namespace = namespace.replace(/-/g, "_");

newfilepath = path.basename(newfilepath, '.cs');

openTemplateAndSaveNewFile(templatetype, namespace, newfilepath, originalfilepath);
// Chomp of .cs and other extension like MyClass.Writer.cs for partial classes.
let classname = newfilename.substring(0, newfilename.indexOf('.'));
openTemplateAndSaveNewFile(templatetype, namespace, classname, originalfilepath);
});
}

function capitalize(word : string) {
return word.charAt(0).toUpperCase() + word.substr(1);
}

function correctExtension(filename) {
if (path.extname(filename) !== '.cs') {
if (filename.endsWith('.')) {
Expand All @@ -109,34 +128,78 @@ function correctExtension(filename) {
return filename;
}

function removeTrailingSeparator(filepath) {
if (filepath[filepath.length - 1] === path.sep) {
filepath = filepath.substr(0, filepath.length - 1);
}
return filepath;
}

function getProjectRootDirOfFilePath(filepath) {
var projectrootdir = parentfinder.sync(path.dirname(filepath), 'project.json');
function getProjectFile(filepath): string {
let projectrootdir = parentfinder.sync(filepath, 'project.json');
if (projectrootdir == null) {
var csprojfiles = findupglob.sync('*.csproj', { cwd: path.dirname(filepath) });
let csprojfiles = findupglob.sync('*.csproj', { cwd: path.dirname(filepath) });
if (csprojfiles == null) {
return null;
}
projectrootdir = path.dirname(csprojfiles[0]);
projectrootdir = csprojfiles[0];
}
return projectrootdir;
}

function openTemplateAndSaveNewFile(type: string, namespace: string, filename: string, originalfilepath: string) {
function getCompilationRoots(projectFile: string): string[] {
let compilationsRoots = <string[]>[];
let parser = new xml2js.Parser({normalize: true});
let projectFileContent = fs.readFileSync(projectFile);
parser.parseString(projectFileContent, (err, result) => {
let itemGroups = result.Project.ItemGroup;
if(itemGroups) {
for(let itemGroup of itemGroups) {
let compileEntries = itemGroup.Compile;
if(compileEntries) {
for(let compileEntry of compileEntries) {
let includePattern = <string>compileEntry.$ && compileEntry.$.Include ?
compileEntry.$.Include : undefined;
if(includePattern) {
let includeFolder = /^[\d\w\s/\\]+(?=\/)/g.exec(includePattern);
if(includeFolder.length == 1) compilationsRoots.push(includeFolder[0]);
}
}
}
}
}
});
return compilationsRoots;
}

function removeCompilationRootFolder(projectFile: string, namespaceTokens: string[]) {
let compilationsRoots = getCompilationRoots(projectFile);
for(let compilationsRoot of compilationsRoots) {
let counter = 0;
let cpRootTokens = compilationsRoot.split(/\\|\//g).filter(token => token.trim.length <= 0);
for(let index = 0; index < cpRootTokens.length; index++) {
if(namespaceTokens.length < index) {
break;
}
if(namespaceTokens[index] === cpRootTokens[index]) {
counter++
} else {
break;
}
}
if(counter > 0) {
for(let count = 0; count < counter; count++) {
namespaceTokens.shift();
}
break;
}
}
return namespaceTokens;
}

function openTemplateAndSaveNewFile(type: string, namespace: string, classname: string, originalfilepath: string) {

let templatefileName = type + '.tmpl';

vscode.workspace.openTextDocument(vscode.extensions.getExtension('jchannon.csharpextensions').extensionPath + '/templates/' + templatefileName)
.then((doc: vscode.TextDocument) => {

let text = doc.getText();
text = text.replace('${namespace}', namespace);
text = text.replace('${classname}', filename);
text = text.replace('${classname}', classname);
let cursorPosition = findCursorInTemlpate(text);
text = text.replace('${cursor}', '');
fs.writeFileSync(originalfilepath, text);
Expand Down