diff --git a/server/antlr/CMake.g4 b/server/antlr/CMake.g4 index 959ff79..c368a46 100644 --- a/server/antlr/CMake.g4 +++ b/server/antlr/CMake.g4 @@ -67,11 +67,11 @@ LineComment // 1. NL between '(' and ')' // 2. NL between command invocations IgnoreNLBetweenArgs - : '\r'? '\n' { this.nesting > 0 }? -> skip + : '\r'? '\n' { this.nesting > 0 }? -> channel(HIDDEN) ; IgnoreExtraNLBetweenCmds - : '\r'? '\n' { this.newLineCount > 0 }? -> skip + : '\r'? '\n' { this.newLineCount > 0 }? -> channel(HIDDEN) ; NL : {this.newLineCount++;} '\r'? '\n' diff --git a/server/src/format.ts b/server/src/format.ts index b471d6c..fdfc378 100644 --- a/server/src/format.ts +++ b/server/src/format.ts @@ -47,7 +47,7 @@ export class FormatListener extends CMakeListener { } else { const tokenType: number = this._tokenStream.get(tokenIndex).type; if (tokenType === CMakeLexer.NL) { - result += ' '.repeat(this.getIndent()) + t.text + result += ' '.repeat(this.getIndent()) + t.text; } else { result += ' '.repeat(this.getIndent() + 4) + t.text; } diff --git a/server/src/format_.ts b/server/src/format_.ts new file mode 100644 index 0000000..1876708 --- /dev/null +++ b/server/src/format_.ts @@ -0,0 +1,225 @@ +import CMakeLexer from "./parser/CMakeLexer"; +import CMakeListener from "./parser/CMakeListener"; +import { incToBaseDir } from "./symbolTable/goToDefination"; + +export class Formatter extends CMakeListener { + private _indent: number; + private _indentLevel: number; + private _tokenStream: any; + private _formatted: string; + + constructor(_indent: number, tokenStream: any) { + super(); + + this._indent = _indent; + this._indentLevel = 0; + this._tokenStream = tokenStream; + this._formatted = ""; + } + + getFormatedText(): string { + return this._formatted; + } + enterFile(ctx: any): void { + for (let token of this._tokenStream.tokens) { + if (token.channel !== CMakeLexer.HIDDEN) { + break; + } + this._formatted += token.text; + } + } + enterAddSubDirCmd(ctx: any): void { + this.enterCommand("add_subdirectory", ctx); + } + enterContinueCmd(ctx: any): void { + this.enterCommand("continue", ctx); + } + enterBreakCmd(ctx: any): void { + this.enterCommand("break", ctx); + } + enterElseCmd(ctx: any): void { + this.enterCommand("else", ctx); + } + enterElseIfCmd(ctx: any): void { + this.enterCommand("elseif", ctx); + } + enterEndForeachCmd(ctx: any): void { + this.enterCommand("endforeach", ctx); + } + enterEndFunctionCmd(ctx: any): void { + this.enterCommand("endfunction", ctx); + } + enterEndIfCmd(ctx: any): void { + this.enterCommand("endif", ctx); + } + enterEndMacroCmd(ctx: any): void { + this.enterCommand("endmacro", ctx); + } + enterEndWhileCmd(ctx: any): void { + this.enterCommand("endwhile", ctx); + } + enterFunctionCmd(ctx: any): void { + this.enterCommand("function", ctx); + } + enterForeachCmd(ctx: any): void { + this.enterCommand("foreach", ctx); + } + enterIfCmd(ctx: any): void { + this.enterCommand("if", ctx); + } + enterIncludeCmd(ctx: any): void { + this.enterCommand("include", ctx); + } + enterMacroCmd(ctx: any): void { + this.enterCommand("macro", ctx); + } + enterOptionCmd(ctx: any): void { + this.enterCommand("option", ctx); + } + enterSetCmd(ctx: any): void { + this.enterCommand("set", ctx); + } + enterWhileCmd(ctx: any): void { + this.enterCommand("while", ctx); + } + enterOtherCmd(ctx: any): void { + const token = ctx.ID().symbol; + this.enterCommand(ctx.ID().symbol.text, ctx); + } + + private getIndent(): number { + return this._indent * this._indentLevel; + } + + private isBlockCmd(cmd: string): boolean { + return ['if', 'elseif', 'else', 'while', 'foreach', 'function', 'macro'].includes(cmd.toLowerCase()); + } + + private isEndBlockCmd(cmd: string): boolean { + return ['elseif', 'else', 'endif', 'endwhile', 'endforeach', 'endfunction', 'endmacro'].includes(cmd.toLowerCase()); + } + + private getHiddenTextOnRight(tokenIndex: number, indent: number): string { + const token = this._tokenStream.get(tokenIndex); + + let result = ""; + const hiddenTokens = this._tokenStream.getHiddenTokensToRight(tokenIndex, CMakeLexer.HIDDEN); + if (hiddenTokens === null) { + return result; + } + + if ((hiddenTokens.length > 0) && + (hiddenTokens[0].type === CMakeLexer.LineComment || hiddenTokens[0].type === CMakeLexer.BracketComment) && + hiddenTokens[0].line === token.line) { + result += ' '; + } + + // for (const t of hiddenTokens) { + // result += t.text; + // } + + let prevLineNo: number = token.line; + hiddenTokens.forEach((t, index) => { + const curLineNo: number = t.line; + if ((curLineNo !== prevLineNo) && + (t.type === CMakeLexer.LineComment || t.type === CMakeLexer.BracketComment)) { + result += ' '.repeat(indent); + } + + result += t.text; + prevLineNo = t.line; + }); + + return result; + } + + private getArgumentText(argCtx: any, indent: number): string { + let result = ""; + const cnt: number = argCtx.getChildCount(); + if (cnt === 1) { + result += argCtx.stop.text; + result += this.getHiddenTextOnRight(argCtx.stop.tokenIndex, indent); + } else { + result += '('; + const lParenIndex: number = argCtx.LParen().symbol.tokenIndex; + result += this.getHiddenTextOnRight(lParenIndex, indent); + const innerCnt = argCtx.argument().length; + argCtx.argument().forEach((innerCtx, index) => { + result += this.getArgumentText(innerCtx, indent); + if (index < innerCnt - 1) { + result += ' '; + } + }); + result += ')'; + const rParenIndex: number = argCtx.RParen().symbol.tokenIndex; + result += this.getHiddenTextOnRight(rParenIndex, indent); + } + + return result; + } + + private enterCommand(cmd: string, ctx: any) { + if (this.isEndBlockCmd(cmd)) { + --this._indentLevel; + if (this._indentLevel < 0) { + this._indentLevel = 0; + } + } + + // indent + this._formatted += " ".repeat(this.getIndent()); + + // the command name and '(' + this._formatted += cmd + '('; + + // comment and newline can be placed after '(' + this._formatted += this.getHiddenTextOnRight(ctx.LParen().symbol.tokenIndex, + this.getIndent() + this._indent + ); + + // all arguments + if (ctx.hasOwnProperty('argument')) { + const cnt: number = ctx.argument().length; + const indent = (this._indentLevel + 1) * this._indent; + const cmdLineNo: number = ctx.LParen().symbol.line; + let prevLineNo: number = cmdLineNo; + let curLineNo: number = -1; + ctx.argument().forEach((argCtx, index, array) => { + curLineNo = argCtx.start.line; + if (curLineNo !== prevLineNo) { + this._formatted += ' '.repeat(indent); + } + + this._formatted += this.getArgumentText(argCtx, indent); + + const next = index + 1; + if (next < cnt) { + if (array[next].start.line === curLineNo) { + this._formatted += ' '; + } + } + + prevLineNo = curLineNo; + }); + } + + // ')' + this._formatted += ')'; + + // get comment on right of command + const rParenIndex: number = ctx.RParen().symbol.tokenIndex; + this._formatted += this.getHiddenTextOnRight(rParenIndex, this.getIndent()); + + // command terminator + this._formatted += '\n'; + const newLineIndex = rParenIndex + 1; + + // consider increase or decrease indent level + if (this.isBlockCmd(cmd)) { + ++this._indentLevel; + } + + // get all comments and newlines after command terminator + this._formatted += this.getHiddenTextOnRight(newLineIndex, this.getIndent()); + } +} \ No newline at end of file diff --git a/server/src/parser/CMakeLexer.js b/server/src/parser/CMakeLexer.js index 07f2b00..8bd7576 100644 --- a/server/src/parser/CMakeLexer.js +++ b/server/src/parser/CMakeLexer.js @@ -89,13 +89,13 @@ const serializedATN = [4,0,31,337,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2, 254,5,35,0,0,251,253,8,2,0,0,252,251,1,0,0,0,253,256,1,0,0,0,254,252,1,0, 0,0,254,255,1,0,0,0,255,257,1,0,0,0,256,254,1,0,0,0,257,258,6,23,0,0,258, 48,1,0,0,0,259,261,5,13,0,0,260,259,1,0,0,0,260,261,1,0,0,0,261,262,1,0, -0,0,262,263,5,10,0,0,263,264,4,24,0,0,264,265,1,0,0,0,265,266,6,24,1,0,266, +0,0,262,263,5,10,0,0,263,264,4,24,0,0,264,265,1,0,0,0,265,266,6,24,0,0,266, 50,1,0,0,0,267,269,5,13,0,0,268,267,1,0,0,0,268,269,1,0,0,0,269,270,1,0, -0,0,270,271,5,10,0,0,271,272,4,25,1,0,272,273,1,0,0,0,273,274,6,25,1,0,274, -52,1,0,0,0,275,277,6,26,2,0,276,278,5,13,0,0,277,276,1,0,0,0,277,278,1,0, +0,0,270,271,5,10,0,0,271,272,4,25,1,0,272,273,1,0,0,0,273,274,6,25,0,0,274, +52,1,0,0,0,275,277,6,26,1,0,276,278,5,13,0,0,277,276,1,0,0,0,277,278,1,0, 0,0,278,279,1,0,0,0,279,280,5,10,0,0,280,54,1,0,0,0,281,283,7,3,0,0,282, 281,1,0,0,0,283,284,1,0,0,0,284,282,1,0,0,0,284,285,1,0,0,0,285,286,1,0, -0,0,286,287,6,27,1,0,287,56,1,0,0,0,288,289,5,40,0,0,289,290,6,28,3,0,290, +0,0,286,287,6,27,2,0,287,56,1,0,0,0,288,289,5,40,0,0,289,290,6,28,3,0,290, 58,1,0,0,0,291,292,5,41,0,0,292,293,6,29,4,0,293,60,1,0,0,0,294,298,3,63, 31,0,295,298,3,65,32,0,296,298,3,67,33,0,297,294,1,0,0,0,297,295,1,0,0,0, 297,296,1,0,0,0,298,62,1,0,0,0,299,300,5,92,0,0,300,301,8,4,0,0,301,64,1, @@ -109,7 +109,7 @@ const serializedATN = [4,0,31,337,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2, 61,30,0,329,330,5,92,0,0,330,332,3,53,26,0,331,327,1,0,0,0,331,328,1,0,0, 0,331,329,1,0,0,0,332,72,1,0,0,0,333,336,8,6,0,0,334,336,3,61,30,0,335,333, 1,0,0,0,335,334,1,0,0,0,336,74,1,0,0,0,15,0,221,232,240,254,260,268,277, -284,297,308,321,325,331,335,5,0,1,0,6,0,0,1,26,0,1,28,1,1,29,2]; +284,297,308,321,325,331,335,5,0,1,0,1,26,0,6,0,0,1,28,1,1,29,2]; const atn = new antlr4.atn.ATNDeserializer().deserialize(serializedATN); diff --git a/server/src/server.ts b/server/src/server.ts index 0038524..daf5fec 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -33,6 +33,7 @@ import { DefinationListener, incToBaseDir, parsedFiles, refToDef, topScope } fro import { getFileContext } from './utils'; import { createLogger } from './logging'; import SemanticDiagnosticsListener, { cmdNameCase } from './semanticDiagnostics'; +import { Formatter } from './format_'; type Word = { text: string, @@ -258,7 +259,7 @@ connection.onDocumentFormatting((params: DocumentFormattingParams) => { const tokenStream = new antlr4.CommonTokenStream(lexer); const parser = new CMakeParser(tokenStream); const tree = parser.file(); - const formatListener = new FormatListener(tabSize, tokenStream); + const formatListener = new Formatter(tabSize, tokenStream); antlr4.tree.ParseTreeWalker.DEFAULT.walk(formatListener, tree); resolve([ {