diff options
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | .travis.yml | 39 | ||||
| -rw-r--r-- | .vscodeignore | 8 | ||||
| -rw-r--r-- | CHANGELOG.md | 4 | ||||
| -rw-r--r-- | README.md | 55 | ||||
| -rw-r--r-- | appveyor.yml | 17 | ||||
| -rw-r--r-- | images/bool.gif | bin | 0 -> 157592 bytes | |||
| -rw-r--r-- | images/ctor.gif | bin | 0 -> 129577 bytes | |||
| -rw-r--r-- | images/declaration.gif | bin | 0 -> 181425 bytes | |||
| -rw-r--r-- | images/definition.gif | bin | 0 -> 103955 bytes | |||
| -rw-r--r-- | images/method.gif | bin | 0 -> 270240 bytes | |||
| -rw-r--r-- | images/param_simple.gif | bin | 0 -> 156452 bytes | |||
| -rw-r--r-- | images/void.gif | bin | 0 -> 271668 bytes | |||
| -rw-r--r-- | package.json | 31 | ||||
| -rw-r--r-- | src/CodeParser/CodeParser.ts | 10 | ||||
| -rw-r--r-- | src/CodeParser/CodeParserController.ts | 101 | ||||
| -rw-r--r-- | src/CodeParser/CppParser.ts | 131 | ||||
| -rw-r--r-- | src/DocGen/CppGen.ts | 153 | ||||
| -rw-r--r-- | src/DocGen/DocGen.ts | 19 | ||||
| -rw-r--r-- | src/extension.ts | 13 | ||||
| -rw-r--r-- | src/test/extension.test.ts | 22 | ||||
| -rw-r--r-- | src/test/index.ts | 22 | ||||
| -rw-r--r-- | tsconfig.json | 16 | ||||
| -rw-r--r-- | tslint.json | 9 |
24 files changed, 654 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c8220a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +.vscode-test/ +.vsix diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..760e13c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,39 @@ +sudo: false + +os: + - linux + - osx + +language: node_js +node_js: + - "6" + +matrix: + include: + - os: linux + dist: trusty + sudo: false + - os: osx + osx_image: xcode9.1 + +stages: + - lint + - build + - test + +before_install: + - if [ $TRAVIS_OS_NAME == "linux" ]; then + export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; + sh -e /etc/init.d/xvfb start; + sleep 3; + fi + +install: + - npm install + +jobs: + include: + - stage: lint + script: tslint -c tslint.json 'src/**/*.ts' + - stage: build + script: npm run vscode:prepublish
\ No newline at end of file diff --git a/.vscodeignore b/.vscodeignore new file mode 100644 index 0000000..ea75c67 --- /dev/null +++ b/.vscodeignore @@ -0,0 +1,8 @@ +.vscode/** +.vscode-test/** +out/test/** +out/**/*.map +src/** +.gitignore +tsconfig.json +vsc-extension-quickstart.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..98a3e86 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +## [0.0.1] +- Initial release
\ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d1086ff --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# Generate Doxygen Comments fro VSCode + +This VSCode Extensions provides Doxygen Documentation generation on the fly by starting a Doxygen comment block and pressing enter. + +[](https://travis-ci.org/christophschlosser/docdoxgen-test) + +## Features + +Generate Doxygen Comments from method signatures. + +From simple setter methods + + + +over declarations + + + +to bool return values. + + + +And some special cases: + +Like Constructors + + + +Defintions in header files + + + +Multi line definitions/declarations + + + +Or void return values + + + +## Extension Settings + +See [Below](#whats-to-come) + +## Known Issues + +Completely new extension, so none. + +## What's to come + +* Suggest smart text + +* Configuration options + +* Tests
\ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..a52d318 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,17 @@ +environment: + ELECTRON_RUN_AS_NODE: 1 + VSCODE_BUILD_VERBOSE: true + nodejs_version: "6" + +image: Visual Studio 2015 + +install: + - ps: Install-Product node $env:nodejs_version + - npm install + - set path=%programfiles(x86)%\\Microsoft SDKs\TypeScript\2.4;%path% + +build_script: + - npm run vscode:prepublish + +test_script: + - npm run test
\ No newline at end of file diff --git a/images/bool.gif b/images/bool.gif Binary files differnew file mode 100644 index 0000000..670b07f --- /dev/null +++ b/images/bool.gif diff --git a/images/ctor.gif b/images/ctor.gif Binary files differnew file mode 100644 index 0000000..3395188 --- /dev/null +++ b/images/ctor.gif diff --git a/images/declaration.gif b/images/declaration.gif Binary files differnew file mode 100644 index 0000000..2ed2aad --- /dev/null +++ b/images/declaration.gif diff --git a/images/definition.gif b/images/definition.gif Binary files differnew file mode 100644 index 0000000..ced18ae --- /dev/null +++ b/images/definition.gif diff --git a/images/method.gif b/images/method.gif Binary files differnew file mode 100644 index 0000000..df25d93 --- /dev/null +++ b/images/method.gif diff --git a/images/param_simple.gif b/images/param_simple.gif Binary files differnew file mode 100644 index 0000000..ed6e2d4 --- /dev/null +++ b/images/param_simple.gif diff --git a/images/void.gif b/images/void.gif Binary files differnew file mode 100644 index 0000000..f5dce48 --- /dev/null +++ b/images/void.gif diff --git a/package.json b/package.json new file mode 100644 index 0000000..406e1fb --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "doxdocgen", + "displayName": "Doxygen Documentation Generator", + "description": "Generate doxygen documentation from source code", + "version": "0.0.1", + "publisher": "cschlosser", + "engines": { + "vscode": "^1.16.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onLanguage:cpp" + ], + "main": "./out/extension", + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./", + "postinstall": "node ./node_modules/vscode/bin/install", + "test": "npm run compile && node ./node_modules/vscode/bin/test" + }, + "devDependencies": { + "typescript": "^2.5.3", + "vscode": "^1.1.5", + "@types/node": "^7.0.43", + "@types/mocha": "^2.2.42", + "tslint": "^5.7.0" + } +}
\ No newline at end of file diff --git a/src/CodeParser/CodeParser.ts b/src/CodeParser/CodeParser.ts new file mode 100644 index 0000000..5d0d0bf --- /dev/null +++ b/src/CodeParser/CodeParser.ts @@ -0,0 +1,10 @@ +import { Position, TextDocumentContentChangeEvent, TextEditor } from "vscode"; + +export default interface ICodeParser { + + /** + * @param {TextEditor} activeEditor The open active Editor where the event came from + * @param {TextDocumentContentChangeEvent} event Something in the document changed + */ + Parse(activeEditor: TextEditor, event: TextDocumentContentChangeEvent); +} diff --git a/src/CodeParser/CodeParserController.ts b/src/CodeParser/CodeParserController.ts new file mode 100644 index 0000000..6583cde --- /dev/null +++ b/src/CodeParser/CodeParserController.ts @@ -0,0 +1,101 @@ +import { Disposable, Position, TextDocumentContentChangeEvent, TextEditor, TextLine, window, workspace } from "vscode"; +import CodeParser from "./CodeParser"; +import CppParser from "./CppParser"; + +/** + * + * Checks if the event matches the specified guidelines and if a parser exists for this language + * + * @export + * @class CodeParserController + */ +export default class CodeParserController { + private disposable: Disposable; + private indicators: string[] = []; + + /** + * Creates an instance of CodeParserController + * + * @memberOf CodeParserController + */ + public constructor() { + const subscriptions: Disposable[] = []; + + this.readConfig(); + + // Hand off the event to the parser if a valid parser is found + workspace.onDidChangeTextDocument((event) => { + const activeEditor: TextEditor = window.activeTextEditor; + if (activeEditor && event.document === activeEditor.document) { + this.onEvent(activeEditor, event.contentChanges[0]); + } + }, this, subscriptions); + + this.disposable = Disposable.from(...subscriptions); + } + + /** + * + * Disposes of the subscriptions + * + * @memberOf CodeParserController + */ + public dispose() { + this.disposable.dispose(); + } + + /*************************************************************************** + Implementation + ***************************************************************************/ + + private readConfig() { + this.indicators.push("/**"); // TODO: make this customizable + } + + private check(activeEditor: TextEditor, event: TextDocumentContentChangeEvent): boolean { + if (activeEditor == null || event.text == null) { + return false; + } + const activeSelection: Position = activeEditor.selection.active; + const activeLine: TextLine = activeEditor.document.lineAt(activeSelection.line); + const activeChar: string = activeLine.text.charAt(activeSelection.character); + const startsWith: boolean = event.text.startsWith("\n") || event.text.startsWith("\r\n"); + + // Check if enter was pressed. Note the ! + if (!((activeChar === "") && startsWith)) { + return false; + } + + const cont: string = activeLine.text.trim(); + let found: boolean = false; + + this.indicators.forEach((element: string) => { + if (element === cont) { // Compare the content from the line with the valid indicators + found = true; + return; + } + }); + + return found; + } + + private onEvent(activeEditor: TextEditor, event: TextDocumentContentChangeEvent) { + if (!this.check(activeEditor, event)) { + return null; + } + + const lang: string = activeEditor.document.languageId; + let parser: CodeParser; + + switch (lang) { + case "cpp": + parser = new CppParser(); + break; + default: + // tslint:disable-next-line:no-console + console.log("No comments can be generated for language: " + lang); + return null; + } + parser.Parse(activeEditor, event).GenerateDoc(); + } +} diff --git a/src/CodeParser/CppParser.ts b/src/CodeParser/CppParser.ts new file mode 100644 index 0000000..e194371 --- /dev/null +++ b/src/CodeParser/CppParser.ts @@ -0,0 +1,131 @@ +import { Position, TextDocumentContentChangeEvent, TextEditor, TextLine } from "vscode"; +import CppGenerator from "../DocGen/CppGen"; +import { IDocGen } from "../DocGen/DocGen"; +import ICodeParser from "./CodeParser"; + +/** + * + * Parses C++ code for methods and signatures + * + * @export + * @class CppParser + * @implements {ICodeParser} + */ +export default class CppParser implements ICodeParser { + private activeEditor: TextEditor; + private activeSelection: Position; + + /** + * @inheritdoc + */ + public Parse(activeEdit: TextEditor, event: TextDocumentContentChangeEvent): IDocGen { + this.activeEditor = activeEdit; + this.activeSelection = this.activeEditor.selection.active; + + const activeLine: TextLine = this.activeEditor.document.lineAt(this.activeEditor.selection.active.line); + + const method: string = this.getMethodText(); + + // Not a method + if (method.length === 0) { + return null; + } + + const returnValue: string[] = this.getReturn(method); + + const params: string[] = this.getParams(method); + + const cppGenerator: IDocGen = new CppGenerator(this.activeEditor, this.activeSelection, params, returnValue); + return cppGenerator; + } + + /*************************************************************************** + Implementation + ***************************************************************************/ + + private getMethodText(): string { + let method: string = ""; + + let nextLine: Position = new Position(this.activeSelection.line + 1, this.activeSelection.character); + + let nextLineTxt: string = this.activeEditor.document.lineAt(nextLine.line).text.trim(); + + // VSCode may enter a * on itself, we don't want that in our method + if (nextLineTxt === "*") { + nextLineTxt = ""; + } + + while (nextLineTxt.length === 0) { // Get first method line + nextLine = new Position(nextLine.line + 1, nextLine.character); + nextLineTxt = this.activeEditor.document.lineAt(nextLine.line).text.trim(); + } + + method += nextLineTxt; + + // Get method end line + while (nextLineTxt.indexOf(")") === -1 && + (nextLineTxt.indexOf(";") === -1 || nextLineTxt.indexOf("}") === -1)) { // Check for method end + nextLine = new Position(nextLine.line + 1, nextLine.character); + nextLineTxt = this.activeEditor.document.lineAt(nextLine.line).text.trim(); + + method += " " + nextLineTxt; + } + + // Not a method but some code in the file + if (method.indexOf(")") === -1) { + return ""; + } + + return method; + } + + private getReturn(method: string): string[] { + const retVals: string[] = []; + + // Remove the parameters from the signature + const returnSignature: string = method.slice(0, method.indexOf("(")).trim(); + + if (returnSignature.indexOf(" ") === -1) { // Constructor or similar + return retVals; + } + + const returnType: string = returnSignature.substr(0, returnSignature.lastIndexOf(" ")); + + switch (returnType) { + case "bool": + retVals.push("true"); + retVals.push("false"); + break; + case "void": + break; + default: + retVals.push(returnType); + break; + } + + return retVals; + } + + private getParams(method: string): string[] { + const params: string[] = []; + + // Get parameters from enclosing brackets + const parameters: string = method.slice(method.indexOf("(")) // Get opening bracket + .slice(1, method.indexOf(")")) // Remove opening bracket + .split(")")[0]; // Get closing bracket + + if (parameters.length === 0) { // No parameters + return params; + } + + let paramArr: string[] = parameters.split(","); + paramArr = paramArr.map((item: string) => { + // Remove any special C++ characters + const clean: string = item.trim().replace(/[&*\[\]]/g, ""); + + return clean.split(" ").pop(); + }); + + return paramArr; + } +} diff --git a/src/DocGen/CppGen.ts b/src/DocGen/CppGen.ts new file mode 100644 index 0000000..1dc70f7 --- /dev/null +++ b/src/DocGen/CppGen.ts @@ -0,0 +1,153 @@ +import { Position, Range, Selection, TextEditor, TextLine, WorkspaceEdit } from "vscode"; +import { DoxygenCommands, IDocGen } from "./DocGen"; + +export default class CppGen implements IDocGen { + private lineStart: string; + private endComment: string; + private commandIndicator: string; + private spaceAfterCommand: string; + private activeEditor: TextEditor; + private position: Position; + private comment: string; + private retVals: string[]; + private params: string[]; + + /** + * @param {TextEditor} actEdit Active editor window + * @param {Position} cursorPosition Where the cursor of the user currently is + * @param {string[]} param The parameter names of the method extracted by the parser + * @param {string[]} returnVals The return values extracted by the parser + */ + public constructor(actEdit: TextEditor, cursorPosition: Position, param: string[], returnVals: string[]) { + this.activeEditor = actEdit; + this.position = cursorPosition; + this.comment = "\n"; // Add the new line after the comment indicator + this.params = param; + this.retVals = returnVals; + } + + /** + * @inheritdoc + */ + public GenerateDoc() { + this.readConfig(); + this.generateComment(); + + const oldPos: Position = this.position; + + const active: Position = this.activeEditor.selection.active; + const anchor: Position = new Position(active.line + 1, active.character); // Start at the next line + const replaceSelection = new Selection(anchor, active); + this.activeEditor.edit((editBuilder) => { + editBuilder.replace(replaceSelection, this.comment); // Insert the comment + }); + + // Set cursor after brief command + this.setCursor(oldPos.line + 3, oldPos.character); + const newSelectActive = new Position(oldPos.line + 3, oldPos.character + DoxygenCommands.detailed.length); + const newSelectPos = new Position(oldPos.line + 3, oldPos.character); + this.activeEditor.selection = new Selection(newSelectPos, newSelectActive); + } + + /*************************************************************************** + Implementation + ***************************************************************************/ + + private readConfig() { + this.lineStart = " * "; // TODO: make this customizable + this.endComment = "*/"; // TODO: make this customizable + this.commandIndicator = "@"; // TODO: make this customizable + this.spaceAfterCommand = " "; // TODO: make this customizable + } + + private indentLine(commentLine: string): string { + const line: TextLine = this.activeEditor.document.lineAt(this.activeEditor.selection.start.line); + const lineTxt: string = line.text; + let stringToIndent: string = ""; + // Find indentation from previous line + for (let i = 0; i < line.firstNonWhitespaceCharacterIndex; i++) { + if (lineTxt.charAt(i) === "\t") { + stringToIndent = stringToIndent + "\t"; + } else if (lineTxt.charAt(i) === " ") { + stringToIndent = stringToIndent + " "; + } + } + const textToInsert = stringToIndent + commentLine; + return textToInsert; + } + + private generateBrief() { + let line: string = ""; + line += this.lineStart; + line += this.commandIndicator; + line += DoxygenCommands.brief; + line += this.spaceAfterCommand; + this.comment += this.indentLine(line); + } + + private generateDetailed() { + let line: string = ""; + line += this.lineStart; + line += "\n"; + this.comment += this.indentLine(line); + line = this.lineStart; + line += DoxygenCommands.detailed + "\n"; + this.comment += this.indentLine(line); + line = this.lineStart; + this.comment += this.indentLine(line); + } + + private generateParams() { + let line: string = ""; + + this.params.forEach((element: string) => { + line = this.lineStart; + line += this.commandIndicator; + line += DoxygenCommands.param + " "; // TODO: Make this customizable + line += element + "\n"; + this.comment += this.indentLine(line); + }); + } + + private generateReturn() { + let line: string = ""; + + this.retVals.forEach((element: string) => { + line = this.lineStart; + line += this.commandIndicator; + line += DoxygenCommands.return + " "; // TODO: Make this customizable + line += element + "\n"; + this.comment += this.indentLine(line); + }); + } + + private generateEnd() { + let line: string = " "; // TODO: Make this customizable + line += this.endComment; + this.comment += this.indentLine(line); + } + + private generateComment() { + this.generateBrief(); + this.comment += "\n"; + this.generateDetailed(); + this.comment += "\n"; + if (this.params.length !== 0) { // Only if we have parameters + this.generateParams(); + } + if (this.retVals.length !== 0) { // Only if we have return values + if (this.params.length !== 0) { + const line: string = this.lineStart + "\n"; + this.comment += this.indentLine(line); + } + this.generateReturn(); + } + this.generateEnd(); + } + + private setCursor(line: number, character: number) { + const indentLen: number = this.indentLine("").length; + const move: Selection = new Selection(line, character, line, character); + this.activeEditor.selection = move; + } +} diff --git a/src/DocGen/DocGen.ts b/src/DocGen/DocGen.ts new file mode 100644 index 0000000..9de9d41 --- /dev/null +++ b/src/DocGen/DocGen.ts @@ -0,0 +1,19 @@ +/** + * Contains the supported doxygen commands + * + * @export + * @enum {number} + */ +export enum DoxygenCommands { + brief = "brief", + return = "return", + param = "param", + detailed = "(Detailed description)", +} + +export interface IDocGen { + /** + * Generate documentation string and write it to the active editor + */ + GenerateDoc(); +} diff --git a/src/extension.ts b/src/extension.ts new file mode 100644 index 0000000..f545b21 --- /dev/null +++ b/src/extension.ts @@ -0,0 +1,13 @@ +"use strict"; +// The module 'vscode' contains the VS Code extensibility API +// Import the module and reference it with the alias vscode in your code below +import * as vscode from "vscode"; +import CodeParserController from "./CodeParser/CodeParserController"; + +// this method is called when your extension is activated +// your extension is activated the very first time the command is executed +export function activate(context: vscode.ExtensionContext) { + const parser = new CodeParserController(); + + context.subscriptions.push(parser); +} diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts new file mode 100644 index 0000000..35bf33f --- /dev/null +++ b/src/test/extension.test.ts @@ -0,0 +1,22 @@ +// +// Note: This example test is leveraging the Mocha test framework. +// Please refer to their documentation on https://mochajs.org/ for help. +// + +// The module 'assert' provides assertion methods from node +import * as assert from "assert"; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +import * as vscode from "vscode"; +import * as myExtension from "../extension"; + +// Defines a Mocha test suite to group tests of similar kind together +suite("Extension Tests", () => { + + // Defines a Mocha unit test + test("Something 1", () => { + assert.equal(-1, [1, 2, 3].indexOf(5)); + assert.equal(-1, [1, 2, 3].indexOf(0)); + }); +}); diff --git a/src/test/index.ts b/src/test/index.ts new file mode 100644 index 0000000..c31164a --- /dev/null +++ b/src/test/index.ts @@ -0,0 +1,22 @@ +// +// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING +// +// This file is providing the test runner to use when running extension tests. +// By default the test runner in use is Mocha based. +// +// You can provide your own test runner if you want to override it by exporting +// a function run(testRoot: string, clb: (error:Error) => void) that the extension +// host can call to run the tests. The test runner is expected to use console.log +// to report the results back to the caller. When the tests are finished, return +// a possible error to the callback or null if none. + +import * as testRunner from "vscode/lib/testrunner"; + +// You can directly control Mocha options by uncommenting the following lines +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info +testRunner.configure({ + ui: "tdd", // the TDD UI is being used in extension.test.ts (suite, test, etc.) + useColors: true, // colored output from test results +}); + +module.exports = testRunner; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8a1d847 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6" + ], + "sourceMap": true, + "rootDir": "src" + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +}
\ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..32fa6e5 --- /dev/null +++ b/tslint.json @@ -0,0 +1,9 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": {}, + "rulesDirectory": [] +}
\ No newline at end of file |