Fix bad formatting on several operators (#605)

Many, many formatter and syntax highlighting improvements.
This commit is contained in:
David Kincaid
2024-06-24 13:48:44 -07:00
committed by GitHub
parent 17af8e20c9
commit c07fe37f30
26 changed files with 357 additions and 65 deletions

View File

@@ -271,6 +271,24 @@
"default": true,
"description": "Whether to reveal the terminal when launching the Godot Editor"
},
"godotTools.formatter.emptyLinesBeforeFunctions": {
"type": "string",
"enum": [
"one",
"two"
],
"enumDescriptions": [
"One line before functions. A more compact style.",
"Two lines before functions. Conforms to the official GDScript style guide."
],
"default": "two",
"description": "Number of blank lines to leave before functions."
},
"godotTools.formatter.denseFunctionDeclarations": {
"type": "boolean",
"default": false,
"description": "Whether extra space should be removed from function declarations"
},
"godotTools.lsp.serverProtocol": {
"type": [
"string"

View File

@@ -1,4 +1,4 @@
import * as path from "path";
import * as path from "node:path";
import * as vscode from "vscode";
import { attemptSettingsUpdate, get_extension_uri, clean_godot_path } from "./utils";
import {
@@ -81,6 +81,10 @@ export function activate(context: vscode.ExtensionContext) {
async function initial_setup() {
const projectVersion = await get_project_version();
if (projectVersion === undefined) {
// TODO: actually handle this?
return;
}
const settingName = `editorPath.godot${projectVersion[0]}`;
const result = verify_godot_version(get_configuration(settingName), projectVersion[0]);
const godotPath = result.godotPath;

View File

@@ -1,6 +1,6 @@
import * as vscode from "vscode";
import * as path from "path";
import * as fs from "fs";
import * as path from "node:path";
import * as fs from "node:fs";
import { format_document } from "./textmate";

View File

View File

View File

@@ -26,6 +26,9 @@ func f():
a= 1.0* .2
a =1.0 * 2.
a = 10**10
a = min(10, 10**10)
a= a% b
a =1%2
@@ -36,3 +39,16 @@ func f():
var v = Vector2( 1 , - 1 )
var w = Vector2(1,10-1)
print( - 1 )
print( 1 - 1 )
print( - 1 + (1-1))
print( - 1 + (-1-1))
if a > - 1:
if a < - 1:
if a == - 1:
pass
return - 1

View File

@@ -26,6 +26,9 @@ func f():
a = 1.0 * .2
a = 1.0 * 2.
a = 10 ** 10
a = min(10, 10 ** 10)
a = a % b
a = 1 % 2
@@ -36,3 +39,16 @@ func f():
var v = Vector2(1, -1)
var w = Vector2(1, 10 - 1)
print(-1)
print(1 - 1)
print(-1 + (1 - 1))
print(-1 + (-1 - 1))
if a > -1:
if a < -1:
if a == -1:
pass
return -1

View File

@@ -0,0 +1,15 @@
func f():
# arithmetic
x += 1
x -= 1
x *= 1
x /= 1
x %= 1
# bitwise
x |= 1
x &= 1
x ~= 1
x /= 1
x >>= 1
x <<= 1

View File

@@ -0,0 +1,15 @@
func f():
# arithmetic
x += 1
x -= 1
x *= 1
x /= 1
x %= 1
# bitwise
x |= 1
x &= 1
x ~= 1
x /= 1
x >>= 1
x <<= 1

View File

@@ -0,0 +1,5 @@
func f():
collision_mask = 1 << 1 | 1 << 3
collision_mask = 1 << 1 & 1 << 3
collision_mask = ~1
collision_mask = 1 ^ ~ 1

View File

@@ -0,0 +1,5 @@
func f():
collision_mask = 1 << 1 | 1 << 3
collision_mask = 1 << 1 & 1 << 3
collision_mask = ~1
collision_mask = 1 ^ ~1

View File

@@ -8,3 +8,7 @@ func f():
func g():
print(true and ( not false ) or ( true))
print(true and not false or not (true) )
func h():
print(true && ( not false ) || ( true))
print(true && not false || not (true) )

View File

@@ -8,3 +8,7 @@ func f():
func g():
print(true and (not false) or (true))
print(true and not false or not (true))
func h():
print(true && (not false) || (true))
print(true && not false || not (true))

View File

@@ -1,6 +1,27 @@
class Test:
func _ready():
pass
func test():
pass
# comments
func with_comments():
pass

View File

@@ -1,3 +1,17 @@
class Test:
func _ready():
pass
func test():
pass
# comments
func with_comments():
pass

View File

@@ -2,14 +2,14 @@ var a = 10
var b := 10
var c: int = 10
func f(b:=10):
return func(c:=10):
func f(b := 10):
return func(c := 10):
pass
func f(b: int=10):
return func(c: int=10):
func f(b: int = 10):
return func(c: int = 10):
pass
func f(b=10):
return func(c=10):
func f(b = 10):
return func(c = 10):
pass

View File

@@ -0,0 +1,10 @@
func f():
const a = preload("res://a.gd")
const b = load("res://b.gd")
var origin: Vector2 = Vector2.ZERO
origin.x = 1
var andigin: Vector2 = Vector2.ZERO
andigin.x = 1
print(a)

View File

@@ -0,0 +1,10 @@
func f():
const a = preload("res://a.gd")
const b = load("res://b.gd")
var origin: Vector2 = Vector2.ZERO
origin.x = 1
var andigin: Vector2 = Vector2.ZERO
andigin.x = 1
print(a)

View File

@@ -0,0 +1,4 @@
func f(x):
match x:
var y when y>20:
pass

View File

@@ -0,0 +1,4 @@
func f(x):
match x:
var y when y > 20:
pass

View File

@@ -69,3 +69,6 @@ var a = $"%Child/GrandChild".some_method()
var a = $Child.get_node('GrandChild').some_method()
var a = $"Child".get_node('GrandChild').some_method()
var a = $"%Child".get_node('GrandChild').some_method()
func f():
$Child.add_child(%Unique)

View File

@@ -69,3 +69,6 @@ var a = $"%Child/GrandChild".some_method()
var a = $Child.get_node('GrandChild').some_method()
var a = $"Child".get_node('GrandChild').some_method()
var a = $"%Child".get_node('GrandChild').some_method()
func f():
$Child.add_child(%Unique)

View File

@@ -21,6 +21,7 @@ export const keywords = [
"master",
"mastersync",
"match",
"when",
"not",
"onready",
"or",
@@ -58,10 +59,14 @@ export const symbols = [
"&=",
"^=",
"|=",
"~=",
"<<=",
">>=",
":=",
"->",
"&",
"|",
"^",
"-",
"+",
"/",

View File

@@ -1,16 +1,16 @@
import { Range, TextDocument, TextEdit } from "vscode";
import * as fs from "fs";
import { Range, type TextDocument, TextEdit, TextLine } from "vscode";
import * as fs from "node:fs";
import * as vsctm from "vscode-textmate";
import * as oniguruma from "vscode-oniguruma";
import { keywords, symbols } from "./symbols";
import { get_extension_uri, createLogger } from "../utils";
import { get_configuration, get_extension_uri, createLogger } from "../utils";
const log = createLogger("formatter.tm");
// Promisify readFile
function readFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (error, data) => error ? reject(error) : resolve(data));
fs.readFile(path, (error, data) => (error ? reject(error) : resolve(data)));
});
}
@@ -22,17 +22,21 @@ const wasmBin = fs.readFileSync(wasmPath).buffer;
const registry = new vsctm.Registry({
onigLib: oniguruma.loadWASM(wasmBin).then(() => {
return {
createOnigScanner(patterns) { return new oniguruma.OnigScanner(patterns); },
createOnigString(s) { return new oniguruma.OnigString(s); }
createOnigScanner(patterns) {
return new oniguruma.OnigScanner(patterns);
},
createOnigString(s) {
return new oniguruma.OnigString(s);
},
};
}),
loadGrammar: (scopeName) => {
if (scopeName === "source.gdscript") {
return readFile(grammarPath).then(data => vsctm.parseRawGrammar(data.toString(), grammarPath));
return readFile(grammarPath).then((data) => vsctm.parseRawGrammar(data.toString(), grammarPath));
}
// console.log(`Unknown scope name: ${scopeName}`);
return null;
}
},
});
interface Token {
@@ -47,6 +51,16 @@ interface Token {
skip?: boolean;
}
class FormatterOptions {
emptyLinesBeforeFunctions: "one" | "two";
denseFunctionDeclarations: boolean;
constructor() {
this.emptyLinesBeforeFunctions = get_configuration("formatter.emptyLinesBeforeFunctions");
this.denseFunctionDeclarations = get_configuration("formatter.denseFunctionDeclarations");
}
}
function parse_token(token: Token) {
if (token.scopes.includes("string.quoted.gdscript")) {
token.string = true;
@@ -65,6 +79,10 @@ function parse_token(token: Token) {
token.type = "symbol";
return;
}
// "preload" is highlighted as a keyword but it behaves like a function
if (token.value === "preload") {
return;
}
if (token.scopes.includes("keyword.language.gdscript")) {
token.type = "keyword";
return;
@@ -79,7 +97,7 @@ function parse_token(token: Token) {
}
}
function between(tokens: Token[], current: number) {
function between(tokens: Token[], current: number, options: FormatterOptions) {
const nextToken = tokens[current];
const prevToken = tokens[current - 1];
const next = nextToken.value;
@@ -89,26 +107,44 @@ function between(tokens: Token[], current: number) {
if (!prev) return "";
if (next === "##") return " ";
if (next === "#") return " ";
if (prevToken.skip && nextToken.skip) return "";
if (prev === "(") return "";
if (nextToken.param) {
if (prev === "-" && tokens[current - 2]?.value === ",") {
return "";
if (options.denseFunctionDeclarations) {
if (prev === "-") {
if (tokens[current - 2]?.value === "=") return "";
if (["keyword", "symbol"].includes(tokens[current - 2].type)) {
return "";
}
if ([",", "("].includes(tokens[current - 2]?.value)) {
return "";
}
}
if (next === "%") return " ";
if (prev === "%") return " ";
if (next === "=") {
if (tokens[current - 2]?.value === ":") return " ";
return "";
}
if (prev === "=") {
if (tokens[current - 3]?.value === ":") return " ";
return "";
}
if (prevToken?.type === "symbol") return " ";
if (nextToken.type === "symbol") return " ";
} else {
if (next === ":") {
if (tokens[current + 1]?.value === "=") return " ";
}
}
if (next === "%") return " ";
if (prev === "%") return " ";
if (next === "=") return "";
if (prev === "=") return "";
if (next === ":=") return "";
if (prev === ":=") return "";
if (prevToken?.type === "symbol") return " ";
if (nextToken.type === "symbol") return " ";
}
if (next === ":") {
if (["var", "const"].includes(tokens[current - 2]?.value)) {
if (tokens[current + 1]?.value !== "=") return "";
if (tokens[current + 1]?.value !== "=") return "";
return " ";
}
@@ -117,7 +153,10 @@ function between(tokens: Token[], current: number) {
if (prev === "@") return "";
if (prev === "-") {
if (tokens[current - 2]?.value === "(") {
if (["keyword", "symbol"].includes(tokens[current - 2].type)) {
return "";
}
if ([",", "(", "["].includes(tokens[current - 2]?.value)) {
return "";
}
}
@@ -134,6 +173,7 @@ function between(tokens: Token[], current: number) {
if (prev === "[" && nextToken.type === "symbol") return "";
if (prev === ":") return " ";
if (prev === ";") return " ";
if (prev === "##") return " ";
if (prev === "#") return " ";
if (next === "=") return " ";
if (prev === "=") return " ";
@@ -157,7 +197,13 @@ function between(tokens: Token[], current: number) {
let grammar = null;
registry.loadGrammar("source.gdscript").then(g => { grammar = g; });
registry.loadGrammar("source.gdscript").then((g) => {
grammar = g;
});
function is_comment(line: TextLine): boolean {
return line.text[line.firstNonWhitespaceCharacterIndex] === "#";
}
export function format_document(document: TextDocument): TextEdit[] {
// quit early if grammar is not loaded
@@ -166,35 +212,67 @@ export function format_document(document: TextDocument): TextEdit[] {
}
const edits: TextEdit[] = [];
const options = new FormatterOptions();
let lineTokens: vsctm.ITokenizeLineResult = null;
let onlyEmptyLinesSoFar = true;
let emptyLineCount = 0;
let firstEmptyLine = 0;
for (let lineNum = 0; lineNum < document.lineCount; lineNum++) {
const line = document.lineAt(lineNum);
// skip empty lines
if (line.isEmptyOrWhitespace) {
if (line.isEmptyOrWhitespace || is_comment(line)) {
// delete empty lines at the beginning of the file
if (onlyEmptyLinesSoFar) {
edits.push(TextEdit.delete(line.rangeIncludingLineBreak));
} else {
// Limit the number of consecutive empty lines
const maxEmptyLines: number = 1;
if (maxEmptyLines === 1) {
if (lineNum < document.lineCount - 1 && document.lineAt(lineNum + 1).isEmptyOrWhitespace) {
edits.push(TextEdit.delete(line.rangeIncludingLineBreak));
}
} else if (maxEmptyLines === 2) {
if (lineNum < document.lineCount - 2 && document.lineAt(lineNum + 1).isEmptyOrWhitespace && document.lineAt(lineNum + 2).isEmptyOrWhitespace) {
edits.push(TextEdit.delete(line.rangeIncludingLineBreak));
}
if (emptyLineCount === 0) {
firstEmptyLine = lineNum;
}
if (!is_comment(line)) {
emptyLineCount++;
}
}
// delete empty lines at the end of the file
if (lineNum === document.lineCount - 1) {
for (let i = lineNum - emptyLineCount + 1; i < document.lineCount; i++) {
edits.push(TextEdit.delete(document.lineAt(i).rangeIncludingLineBreak));
}
}
continue;
}
onlyEmptyLinesSoFar = false;
// delete consecutive empty lines
if (emptyLineCount) {
let maxEmptyLines = 1;
const start = line.text.trimStart();
if (options.emptyLinesBeforeFunctions === "two") {
if (start.startsWith("func") || start.startsWith("static func")) {
maxEmptyLines++;
}
}
if (start.startsWith("class")) {
maxEmptyLines++;
}
let i = 0;
let deletedLines = 0;
const linesToDelete = emptyLineCount - maxEmptyLines;
while (i < lineNum && deletedLines < linesToDelete) {
const candidate = document.lineAt(firstEmptyLine + i++);
if (candidate.isEmptyOrWhitespace) {
edits.push(TextEdit.delete(candidate.rangeIncludingLineBreak));
deletedLines++;
}
}
emptyLineCount = 0;
}
// skip comments
if (line.text[line.firstNonWhitespaceCharacterIndex] === "#") {
if (is_comment(line)) {
continue;
}
@@ -228,7 +306,7 @@ export function format_document(document: TextDocument): TextEdit[] {
if (i > 0 && tokens[i - 1].string === true && tokens[i].string === true) {
nextLine += tokens[i].original;
} else {
nextLine += between(tokens, i) + tokens[i].value.trim();
nextLine += between(tokens, i, options) + tokens[i].value.trim();
}
}

View File

@@ -1,8 +1,8 @@
import * as vscode from "vscode";
import * as path from "path";
import * as fs from "fs";
import * as os from "os";
import { execSync } from "child_process";
import * as path from "node:path";
import * as fs from "node:fs";
import * as os from "node:os";
import { execSync } from "node:child_process";
let projectDir: string | undefined = undefined;
let projectFile: string | undefined = undefined;

View File

@@ -55,11 +55,10 @@
{ "include": "#nodepath_object" },
{ "include": "#nodepath_function" },
{ "include": "#strings" },
{ "include": "#builtin_classes" },
{ "include": "#const_vars" },
{ "include": "#keywords" },
{ "include": "#logic_operator" },
{ "include": "#compare_operator" },
{ "include": "#arithmetic_operator" },
{ "include": "#operators" },
{ "include": "#lambda_declaration" },
{ "include": "#class_declaration" },
{ "include": "#variable_declaration" },
@@ -77,13 +76,12 @@
{ "include": "#func" },
{ "include": "#letter" },
{ "include": "#numbers" },
{ "include": "#builtin_classes" },
{ "include": "#pascal_case_class" },
{ "include": "#line_continuation" }
]
},
"comment": {
"match": "(#).*$\\n?",
"match": "(##|#).*$\\n?",
"name": "comment.line.number-sign.gdscript",
"captures": { "1": { "name": "punctuation.definition.comment.number-sign.gdscript" } }
},
@@ -97,14 +95,37 @@
"name": "constant.character.escape.gdscript",
"match": "\\\\."
},
{ "include": "#string_formatting" }
{ "include": "#string_percent_placeholders" },
{ "include": "#string_bracket_placeholders" }
]
},
"string_formatting": {
"string_percent_placeholders": {
"name": "meta.format.percent.gdscript",
"match": "(?x)\n (\n % (\\([\\w\\s]*\\))?\n [-+#0 ]*\n (\\d+|\\*)? (\\.(\\d+|\\*))?\n ([hlL])?\n [diouxXeEfFgGcrsab%]\n )\n",
"captures": { "1": { "name": "constant.character.format.placeholder.other.gdscript" } }
},
"string_bracket_placeholders": {
"patterns": [
{
"name": "meta.format.brace.gdscript",
"match": "(?x)\n (\n {{ | }}\n | (?:\n {\n \\w* (\\.[[:alpha:]_]\\w* | \\[[^\\]'\"]+\\])*\n (![rsa])?\n ( : \\w? [<>=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )?\n })\n )\n",
"captures": {
"1": { "name": "constant.character.format.placeholder.other.gdscript" },
"3": { "name": "storage.type.format.gdscript" },
"4": { "name": "storage.type.format.gdscript" }
}
},
{
"name": "meta.format.brace.gdscript",
"match": "(?x)\n (\n {\n \\w* (\\.[[:alpha:]_]\\w* | \\[[^\\]'\"]+\\])*\n (![rsa])?\n (:)\n [^'\"{}\\n]* (?:\n \\{ [^'\"}\\n]*? \\} [^'\"{}\\n]*\n )*\n }\n )\n",
"captures": {
"1": { "name": "constant.character.format.placeholder.other.gdscript" },
"3": { "name": "storage.type.format.gdscript" },
"4": { "name": "storage.type.format.gdscript" }
}
}
]
},
"nodepath_object": {
"name": "meta.literal.nodepath.gdscript",
"begin": "(NodePath)\\s*(?:\\()",
@@ -154,10 +175,6 @@
"match": "\\bfunc\\b",
"name": "keyword.language.gdscript"
},
"logic_operator": {
"match": "\\b(and|or|not|!)\\b",
"name": "keyword.operator.wordlike.gdscript"
},
"in_keyword": {
"patterns": [
{
@@ -180,12 +197,33 @@
}
]
},
"operators": {
"patterns": [
{ "include": "#wordlike_operator" },
{ "include": "#boolean_operator" },
{ "include": "#arithmetic_operator" },
{ "include": "#bitwise_operator" },
{ "include": "#compare_operator" }
]
},
"wordlike_operator": {
"match": "\\b(and|or|not)\\b",
"name": "keyword.operator.wordlike.gdscript"
},
"boolean_operator": {
"match": "(&&|\\|\\|)",
"name": "keyword.operator.boolean.gdscript"
},
"bitwise_operator": {
"match": "&|\\||<<=|>>=|<<|>>|\\^|~",
"name": "keyword.operator.bitwise.gdscript"
},
"compare_operator": {
"match": "<=|>=|==|<|>|!=",
"match": "<=|>=|==|<|>|!=|!",
"name": "keyword.operator.comparison.gdscript"
},
"arithmetic_operator": {
"match": "->|\\+=|-=|\\*=|/=|%=|&=|\\|=|\\*|/|%|\\+|-|<<|>>|&|\\||\\^|~|!",
"match": "->|\\+=|-=|\\*=|\\^=|/=|%=|&=|~=|\\|=|\\*\\*|\\*|/|%|\\+|-",
"name": "keyword.operator.arithmetic.gdscript"
},
"assignment_operator": {
@@ -193,11 +231,11 @@
"name": "keyword.operator.assignment.gdscript"
},
"control_flow": {
"match": "\\b(?:if|elif|else|while|break|continue|pass|return|match|yield|await)\\b",
"match": "\\b(?:if|elif|else|while|break|continue|pass|return|match|when|yield|await)\\b",
"name": "keyword.control.gdscript"
},
"keywords": {
"match": "\\b(?:class|class_name|is|onready|tool|static|export|as|void|enum|preload|assert|breakpoint|rpc|sync|remote|master|puppet|slave|remotesync|mastersync|puppetsync|trait|namespace)\\b",
"match": "\\b(?:class|class_name|is|onready|tool|static|export|as|void|enum|preload|assert|breakpoint|sync|remote|master|puppet|slave|remotesync|mastersync|puppetsync|trait|namespace)\\b",
"name": "keyword.language.gdscript"
},
"letter": {
@@ -397,7 +435,7 @@
"name": "constant.language.gdscript"
},
"pascal_case_class": {
"match": "\\b([A-Z][a-z_0-9]*([A-Z]?[a-z_0-9]+)*[A-Z]?)\\b",
"match": "\\b([A-Z]+[a-z_0-9]*([A-Z]?[a-z_0-9]+)*[A-Z]?)\\b",
"name": "entity.name.type.class.gdscript"
},
"signal_declaration_bare": {
@@ -494,7 +532,7 @@
"beginCaptures": {
"1": { "name": "variable.parameter.function.language.gdscript" },
"2": { "name": "punctuation.separator.annotation.gdscript" },
"3": { "name": "entity.name.type.class.builtin.gdscript" }
"3": { "name": "entity.name.type.class.gdscript" }
},
"end": "(,)|(?=\\))",
"endCaptures": { "1": { "name": "punctuation.separator.parameters.gdscript" } },