diff --git a/.vscode/launch.json b/.vscode/launch.json index 3fa7569..ecc7986 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "outFiles": [ "${workspaceFolder}/dist/**/*.js" ], - "preLaunchTask": "npm: compile-tsc-debug" + "preLaunchTask": "npm: webpack-debug" } ] } diff --git a/GodotDebugSession/Logger.cs b/GodotDebugSession/Logger.cs index 09b6c74..de41b18 100644 --- a/GodotDebugSession/Logger.cs +++ b/GodotDebugSession/Logger.cs @@ -13,43 +13,41 @@ namespace GodotDebugSession private static readonly string LogPath = $"{ThisAppPathWithoutExtension}.log"; internal static readonly string NewLogPath = $"{ThisAppPathWithoutExtension}.new.log"; - private static StreamWriter NewWriter() => new StreamWriter(LogPath, append: true, Encoding.UTF8); + private static StreamWriter _writer; - private static void Log(StreamWriter writer, string message) - { - writer.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}: {message}"); - } + private static StreamWriter Writer => + _writer ?? (_writer = new StreamWriter(LogPath, append: true, Encoding.UTF8)); - public static void Log(string message) + private static void WriteLog(string message) { - using (var writer = NewWriter()) + try { - Log(writer, message); + var writer = Writer; + writer.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}: {message}"); + writer.Flush(); + } + catch (IOException e) + { + Console.Error.WriteLine(e); } } - public static void LogError(string message) - { - using (var writer = NewWriter()) - { - Log(writer, message); - } - } + public static void Log(string message) => + WriteLog(message); - public static void LogError(string message, Exception ex) - { - using (var writer = NewWriter()) - { - Log(writer, $"{message}\n{ex}"); - } - } + public static void LogError(string message) => + WriteLog(message); - public static void LogError(Exception ex) + public static void LogError(string message, Exception ex) => + WriteLog($"{message}\n{ex}"); + + public static void LogError(Exception ex) => + WriteLog(ex.ToString()); + + public static void Close() { - using (var writer = NewWriter()) - { - Log(writer, ex.ToString()); - } + _writer?.Close(); + _writer = null; } } } diff --git a/GodotDebugSession/Program.cs b/GodotDebugSession/Program.cs index 9c763ae..7fb4256 100644 --- a/GodotDebugSession/Program.cs +++ b/GodotDebugSession/Program.cs @@ -20,6 +20,10 @@ namespace GodotDebugSession { Logger.LogError(ex); } + finally + { + Logger.Close(); + } } } } diff --git a/README.md b/README.md index fa56575..cf85d3b 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,35 @@ Debugger and utilities for working with Godot C# projects in VSCode. +## Requirements + +**Godot 3.2.2** or greater. Older versions of Godot are not supported. + ## Features - Debugging. -- Launch a game directly from the Godot editor. -- Code completion for Node paths, Input actions, Resource paths, Scene paths and Signal names. +- Launch a game directly in the Godot editor from VSCode. +- Additional code completion for Node paths, Input actions, Resource paths, Scene paths and Signal names. -## Requirements +**NOTES:** +- A running Godot instance must be editing the project in order for `Play in Editor` and the code completion to work. +- Node path suggestions are provided from the currently edited scene in the Godot editor. +- Currently Signal suggestions are only provided using the information from the project build +results, not from the information in the edited document. This will change in the future. -Godot 3.2.3 or 4.0 or greater is required (both versions are currently unreleaded). +## Debugger launch configurations -A Godot editor instance must be running for the project in order for code completion to work. +By default the extension creates the following launch configurations: + +- **Play in Editor**\ + Launches the game in the Godot editor for debugging in VSCode.\ + For this option to work, a running Godot instance must be editing the project. +- **Launch**\ + Launches the game with a Godot executable for debugging in VSCode.\ + Before using this option, the value of the _"executable"_ property must be changed + to a path that points to the Godot executable that will be launched launched. +- **Attach**\ + Attaches to a running Godot instance that was configured to listen for a debugger connection. ## Screenshots diff --git a/package.json b/package.json index 9230aea..c966781 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,10 @@ "compile": "make build", "compile-tsc": "make tsc", "compile-tsc-debug": "make tsc-debug", - "watch": "tsc -watch -p ./" + "watch": "tsc -watch -p ./", + "webpack": "webpack --mode production", + "webpack-debug": "webpack --mode development", + "webpack-watch": "webpack --mode development --watch" }, "dependencies": { "chokidar": "^3.4.0", @@ -46,7 +49,7 @@ "glob": "^7.1.4", "make": "^0.8.1", "mocha": "^6.1.4", - "ts-loader": "^7.0.3", + "ts-loader": "^7.0.5", "tslint": "^5.12.1", "typescript": "^3.3.1", "vsce": "^1.20.0", diff --git a/src/completion-provider.ts b/src/completion-provider.ts index 75dd283..3579b81 100644 --- a/src/completion-provider.ts +++ b/src/completion-provider.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import { Client, MessageStatus } from './godot-tools-messaging/client'; +import { fixPathForGodot } from './godot-utils'; enum CompletionKind { InputActions = 0, @@ -49,7 +50,7 @@ export class GodotCompletionProvider implements vscode.CompletionItemProvider { return undefined; } - let filePath = document.uri.fsPath; + let filePath = fixPathForGodot(document.uri.fsPath); let [lines, character] = this.getPrefixLines(document, position); let linePrefix = lines.substr(0, character); diff --git a/src/extension.ts b/src/extension.ts index 9b41fff..c6cb128 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { DebugProtocol } from 'vscode-debugprotocol'; import { Client, Peer, MessageContent, MessageStatus, ILogger, IMessageHandler } from './godot-tools-messaging/client'; import * as completion_provider from './completion-provider'; +import { fixPathForGodot } from './godot-utils'; import * as path from 'path'; import * as fs from 'fs'; @@ -91,7 +92,13 @@ export function activate(context: vscode.ExtensionContext) { let godotProjectFile = path.join(rootPath, 'project.godot'); return fs.existsSync(godotProjectFile); - })!.uri.path; + })?.uri.fsPath; + + if (godotProjectDir === undefined) { + return; + } + + godotProjectDir = fixPathForGodot(godotProjectDir); client = new Client('VisualStudioCode', godotProjectDir, new MessageHandler(), new Logger()); client.start(); @@ -129,7 +136,9 @@ class GodotMonoDebugConfigProvider implements vscode.DebugConfigurationProvider debugConfiguration.__exceptionOptions = convertToExceptionOptions(getModel()); } - debugConfiguration['godotProjectDir'] = folder?.uri.fsPath; + if (folder !== undefined) { + debugConfiguration['godotProjectDir'] = folder.uri.fsPath; + } return debugConfiguration; } diff --git a/src/godot-tools-messaging/client.ts b/src/godot-tools-messaging/client.ts index 1d963d8..2b63534 100644 --- a/src/godot-tools-messaging/client.ts +++ b/src/godot-tools-messaging/client.ts @@ -4,7 +4,6 @@ import * as path from 'path'; import * as chokidar from 'chokidar'; import PromiseSocket from 'promise-socket'; import { Disposable } from 'vscode'; -import { throws } from 'assert'; async function timeout(ms: number) { return new Promise(resolve => { @@ -35,7 +34,8 @@ class CustomSocket extends PromiseSocket { let indexOfLineBreak = this.buffer.indexOf('\n'); if (indexOfLineBreak >= 0) { - let line = this.buffer.substring(0, indexOfLineBreak); + let hasCR = indexOfLineBreak !== 0 && this.buffer.charAt(indexOfLineBreak - 1) === '\r'; + let line = this.buffer.substring(0, hasCR ? indexOfLineBreak - 1 : indexOfLineBreak); this.buffer = this.buffer.substring(indexOfLineBreak + 1); return line; } @@ -269,24 +269,46 @@ export class Client implements Disposable { return; } - const socket = new CustomSocket(); + const attempts = 3; + let attemptsLeft = attempts; - this.logger.logInfo('Connecting to Godot Ide Server'); + while (attemptsLeft-- > 0) { + if (attemptsLeft < (attempts - 1)) { + this.logger.logInfo(`Waiting 3 seconds... (${attemptsLeft + 1} attempts left)`); + await timeout(5000); + } - await socket.connect(this.metadata!.port, 'localhost'); + const socket = new CustomSocket(); - this.logger.logInfo('Connection open with Godot Ide Server'); + this.logger.logInfo('Connecting to Godot Ide Server'); - this.peer = new Peer(socket, new ClientHandshake(), this.messageHandler, this.logger); + try { + await socket.connect(this.metadata!.port, 'localhost'); + } + catch (err) { + this.logger.logError('Failed to connect to Godot Ide Server', err); + continue; + } + + this.logger.logInfo('Connection open with Godot Ide Server'); + + this.peer?.dispose(); + this.peer = new Peer(socket, new ClientHandshake(), this.messageHandler, this.logger); + + if (!await this.peer.doHandshake(this.identity)) { + this.logger.logError('Handshake failed'); + this.peer.dispose(); + continue; + } + + await this.peer.process(); + + this.logger.logInfo('Connection closed with Ide Client'); - if (!await this.peer.doHandshake(this.identity)) { - this.logger.logError('Handshake failed'); return; } - await this.peer.process(); - - this.logger.logInfo('Connection closed with Ide Client'); + this.logger.logInfo(`Failed to connect to Godot Ide Server after ${attempts} attempts`); } startWatching(): void { diff --git a/src/godot-utils.ts b/src/godot-utils.ts new file mode 100644 index 0000000..b0f52a0 --- /dev/null +++ b/src/godot-utils.ts @@ -0,0 +1,15 @@ + +export function fixPathForGodot(path: string): string { + if (process.platform === "win32") { + // Godot expects the drive letter to be upper case + if (path && path.charAt(1) === ':') { + let drive = path[0]; + let driveUpper = drive.toUpperCase(); + if (driveUpper !== drive) { + path = driveUpper + path.substr(1); + } + } + } + + return path; +}