summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CodeParser/CodeParser.ts10
-rw-r--r--src/CodeParser/CodeParserController.ts101
-rw-r--r--src/CodeParser/CppParser.ts131
-rw-r--r--src/DocGen/CppGen.ts153
-rw-r--r--src/DocGen/DocGen.ts19
-rw-r--r--src/extension.ts13
-rw-r--r--src/test/extension.test.ts22
-rw-r--r--src/test/index.ts22
8 files changed, 471 insertions, 0 deletions
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;