From 6c4963827d36954eea70ecdfb3448b8ec348a130 Mon Sep 17 00:00:00 2001 From: BehrRiley Date: Wed, 1 Jul 2026 02:35:25 -0400 Subject: [PATCH 1/3] draft of buffer work --- DenizenLangServer/DiagnosticProvider.cs | 4 +- .../Services/TextDocumentService.cs | 6 +-- DenizenLangServer/WorkspaceTracker.cs | 21 ++++++-- extension/package-lock.json | 4 +- extension/src/extension.ts | 49 ++++++++++--------- 5 files changed, 50 insertions(+), 34 deletions(-) diff --git a/DenizenLangServer/DiagnosticProvider.cs b/DenizenLangServer/DiagnosticProvider.cs index 4ac4ef6..d8cb735 100644 --- a/DenizenLangServer/DiagnosticProvider.cs +++ b/DenizenLangServer/DiagnosticProvider.cs @@ -53,7 +53,7 @@ public async void LintCheckLoopThread() needsUpdate = NeedsNewDiag; NeedsNewDiag = false; loops++; - if (loops > 60 && DiagDoc != null && DiagDoc.Uri.AbsolutePath.EndsWith(".dsc")) + if (loops > 60 && DiagDoc != null && (DiagDoc.Uri.AbsolutePath.EndsWith(".dsc") || DiagDoc.Uri.Scheme == "untitled")) { loops = 0; needsUpdate = true; @@ -131,7 +131,7 @@ public void LintDocument(TextDocument document) { checker.Run(); PublishCheckerResults(document.Uri, checker); - WorkspaceTracker.Replace(document.Uri, checker); + WorkspaceTracker.Replace(document.Uri, checker, document.Content); } catch (Exception ex) { diff --git a/DenizenLangServer/Services/TextDocumentService.cs b/DenizenLangServer/Services/TextDocumentService.cs index 5f93d0c..4069b32 100644 --- a/DenizenLangServer/Services/TextDocumentService.cs +++ b/DenizenLangServer/Services/TextDocumentService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -34,7 +34,7 @@ public Hover Hover(TextDocumentIdentifier textDocument, Position position, Cance } // TODO: All this code is a dirty "it works" vertical slice mess that needs to be cleaned up TextDocument doc = GetDocument(textDocument); - if (doc == null || !textDocument.Uri.AbsolutePath.EndsWith(".dsc")) + if (doc == null || !(textDocument.Uri.AbsolutePath.EndsWith(".dsc") || textDocument.Uri.Scheme == "untitled")) { return null; } @@ -272,7 +272,7 @@ public CompletionList Completion(TextDocumentIdentifier textDocument, Position p return null; } TextDocument doc = GetDocument(textDocument); - if (doc == null || !textDocument.Uri.AbsolutePath.EndsWith(".dsc")) + if (doc == null || !(textDocument.Uri.AbsolutePath.EndsWith(".dsc") || textDocument.Uri.Scheme == "untitled")) { return new CompletionList(EmptyCompletionItems); } diff --git a/DenizenLangServer/WorkspaceTracker.cs b/DenizenLangServer/WorkspaceTracker.cs index 4393d6b..5d055a3 100644 --- a/DenizenLangServer/WorkspaceTracker.cs +++ b/DenizenLangServer/WorkspaceTracker.cs @@ -1,4 +1,4 @@ -using FreneticUtilities.FreneticExtensions; +using FreneticUtilities.FreneticExtensions; using FreneticUtilities.FreneticToolkit; using SharpDenizenTools.ScriptAnalysis; using System; @@ -15,6 +15,8 @@ public static class WorkspaceTracker { public static ConcurrentDictionary Checkers = new(); + public static ConcurrentDictionary UntitledPayloads = new(); + public static volatile ScriptingWorkspaceData WorkspaceData = null; public static long LastUpdate = 0; @@ -37,12 +39,16 @@ private static void AddInternal(Uri file, ScriptChecker checker) Checkers[FixPath(file)] = checker; } - public static void Replace(Uri file, ScriptChecker checker) + public static void Replace(Uri file, ScriptChecker checker, string content = null) { if (!ClientConfiguration.TrackFullWorkspace || WorkspacePath is null) { return; } + if (file.Scheme == "untitled" && content != null) + { + UntitledPayloads[FixPath(file)] = content; + } AddInternal(file, checker); long index = ++LastUpdate; Task.Factory.StartNew(() => { UpdateWorkspaceData(index); }); @@ -56,6 +62,10 @@ public static string FixPath(Uri uri) { return null; } + if (uri.Scheme == "untitled") + { + return uri.ToString(); + } string path = uri.ToString()["file://".Length..]; // Microsoft always puts a preceding '/' on their corrupt escaped URIs. // If on Windows: preceding '/' is invalid, and MUST be stripped. On Microsoft's own operating system. @@ -77,6 +87,10 @@ public static string FixPath(Uri uri) public static Uri PathToUri(string path) { + if (path.StartsWith("untitled:")) + { + return new(path); + } if (path[0..3].Contains(':')) { path = $"/{Uri.EscapeDataString(path)}"; @@ -122,7 +136,8 @@ public static void UpdateWorkspaceData(long updateCounter) } foreach ((string path, _) in copyCheckers) { - ScriptChecker checker = new(File.ReadAllText(path)) + string text = UntitledPayloads.TryGetValue(path, out string payload) ? payload : File.ReadAllText(path); + ScriptChecker checker = new(text) { SurroundingWorkspace = genData }; diff --git a/extension/package-lock.json b/extension/package-lock.json index 73e3d52..5ffc108 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "denizenscript", - "version": "1.4.8", + "version": "1.4.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "denizenscript", - "version": "1.4.8", + "version": "1.4.9", "license": "MIT", "dependencies": { "vscode-languageclient": "^7.0.0" diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 5922230..f7925da 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -27,7 +27,7 @@ class HighlightCache { let HLCaches : Map = new Map(); function getCache(path : string) { - let result : HighlightCache = HLCaches.get(path); + let result = HLCaches.get(path); if (result) { return result; } @@ -50,7 +50,10 @@ function activateLanguageServer(context: vscode.ExtensionContext, dotnetPath : s debug: { command: dotnetPath, args: [pathFile, "--debug"], options: { cwd: pathDir } } } let clientOptions: languageClient.LanguageClientOptions = { - documentSelector: ["denizenscript"], + documentSelector: [ + { scheme: 'file', language: 'denizenscript' }, + { scheme: 'untitled', language: 'denizenscript' } + ], synchronize: { configurationSection: "denizenscript", }, @@ -118,19 +121,19 @@ const colorTypes : string[] = [ function loadAllColors() { configuration = vscode.workspace.getConfiguration(); for (const i in colorTypes) { - let str : string = configuration.get("denizenscript.theme_colors." + colorTypes[i]); + let str = configuration.get("denizenscript.theme_colors." + colorTypes[i]); if (str === undefined) { outputChannel.appendLine("Missing color config for " + colorTypes[i]); continue; } colorSet(colorTypes[i], str); } - headerSymbols = configuration.get("denizenscript.header_symbols"); - debugHighlighting = configuration.get("denizenscript.debug.highlighting"); - debugFolding = configuration.get("denizenscript.debug.folding"); - doInlineColors = configuration.get("denizenscript.behaviors.do_inline_colors"); - displayDarkColors = configuration.get("denizenscript.behaviors.display_dark_colors"); - const customColors : string = configuration.get("denizenscript.theme_colors.text_color_map"); + headerSymbols = configuration.get("denizenscript.header_symbols") ?? ""; + debugHighlighting = configuration.get("denizenscript.debug.highlighting") ?? false; + debugFolding = configuration.get("denizenscript.debug.folding") ?? false; + doInlineColors = configuration.get("denizenscript.behaviors.do_inline_colors") ?? false; + displayDarkColors = configuration.get("denizenscript.behaviors.display_dark_colors") ?? false; + const customColors = configuration.get("denizenscript.theme_colors.text_color_map") ?? ""; const colorsSplit : string[] = customColors.split(','); for (const i in colorsSplit) { const color = colorsSplit[i]; @@ -153,8 +156,7 @@ let refreshTimer: NodeJS.Timer | undefined = undefined; function refreshDecor() { refreshTimer = undefined; for (const editor of vscode.window.visibleTextEditors) { - const uri = editor.document.uri.toString(); - if (!uri.endsWith(".dsc")) { + if (editor.document.languageId !== 'denizenscript') { continue; } decorateFullFile(editor); @@ -215,7 +217,7 @@ function decorateTag(tag : string, start: number, lineNumber: number, decoration inTagCounter--; if (inTagCounter == 0) { const tagText : string = tag.substring(tagStart + 1, i); - let autoColor : string = getTagColor(tagText, textColor); + const autoColor = getTagColor(tagText, textColor); if (autoColor != null) { addDecor(decorations, "auto:" + autoColor, lineNumber, start + tagStart + 1, start + i); addDecor(decorations, "tag", lineNumber, start + tagStart, start + tagStart + 1); @@ -364,7 +366,7 @@ function isHex(text : string) : boolean { return true; } -function getColorData(color : string) : string { +function getColorData(color : string) : string | null { if (color.startsWith("#")) { return color; } @@ -378,7 +380,7 @@ function getColorData(color : string) : string { return null; } -function fixDark(color : string) { +function fixDark(color : string) : string | null { if (color == null) { return null; } @@ -399,7 +401,7 @@ function fixDark(color : string) { return color; } -function getTagColor(tagText : string, preColor : string) : string { +function getTagColor(tagText : string, preColor : string) : string | null { if (!doInlineColors) { return null; } @@ -415,7 +417,7 @@ function getTagColor(tagText : string, preColor : string) : string { } const formatter : string = formatCodes[tagText]; if (formatter) { - const rgb : string = getColorData(preColor); + const rgb = getColorData(preColor); if (rgb) { if (formatter == "bold") { return rgb + "|weight=bold"; @@ -482,7 +484,7 @@ function decorateArg(arg : string, start: number, lineNumber: number, decoration inTagCounter--; if (inTagCounter == 0) { const tagText : string = arg.substring(tagStart + 1, i); - let autoColor : string = getTagColor(tagText, textColor); + const autoColor = getTagColor(tagText, textColor); if (autoColor != null) { addDecor(decorations, "tag", lineNumber, start + tagStart, start + tagStart + 1); addDecor(decorations, "auto:" + autoColor, lineNumber, start + tagStart + 1, start + i); @@ -962,7 +964,7 @@ async function activateDotNet() { try { outputChannel.appendLine("DenizenScript extension attempting to acquire .NET 8"); const requestingExtensionId = 'DenizenScript.denizenscript'; - const result = await vscode.commands.executeCommand('dotnet.acquire', { version: '8.0', requestingExtensionId }); + const result = await vscode.commands.executeCommand('dotnet.acquire', { version: '8.0', requestingExtensionId }); outputChannel.appendLine("DenizenScript extension NET 8 Acquire result: " + result + ": " + result["dotnetPath"]); return result["dotnetPath"]; } @@ -992,7 +994,7 @@ function applyConfigColors() { let color = ""; if (val.startsWith("<") && val.endsWith(">")) { for (const tag of val.slice(1, -1).split("><")) { - const newColor : string = getTagColor(tag, color); + const newColor = getTagColor(tag, color); if (newColor) { color = newColor; } @@ -1061,14 +1063,14 @@ export async function activate(context: vscode.ExtensionContext) { activateLanguageServer(context, path); activateHighlighter(context); vscode.workspace.onDidOpenTextDocument(doc => { - if (doc.uri.toString().endsWith(".dsc")) { + if (doc.languageId === 'denizenscript') { tryLoadConfigYaml(doc); forceRefresh("onDidOpenTextDocument"); } }, null, context.subscriptions); vscode.workspace.onDidChangeTextDocument(event => { - const curFile : string = event.document.uri.toString(); - if (curFile.endsWith(".dsc")) { + if (event.document.languageId === 'denizenscript') { + const curFile = event.document.uri.toString(); let highlight : HighlightCache = getCache(curFile); event.contentChanges.forEach(change => { if (highlight.needRefreshStartLine == -1 || change.range.start.line < highlight.needRefreshStartLine) { @@ -1091,8 +1093,7 @@ export async function activate(context: vscode.ExtensionContext) { }, null, context.subscriptions); vscode.window.onDidChangeVisibleTextEditors(editors => { for (const editor of editors) { - const uri = editor.document.uri.toString(); - if (!uri.endsWith(".dsc")) { + if (editor.document.languageId !== 'denizenscript') { continue; } tryLoadConfigYaml(editor.document); From 7aa851ddd6ea83fb229572501aa1da1daa69976d Mon Sep 17 00:00:00 2001 From: BehrRiley Date: Wed, 1 Jul 2026 02:54:21 -0400 Subject: [PATCH 2/3] add support for unsaved buffers - add `{ scheme: 'untitled' }` to the LSP document selector - update `WorkspaceTracker` to store and provide payloads for untitled files so `ScriptChecker` can lint them - refactor extension string checks - add an `IsDenizenDocument` helper --- DenizenLangServer/DiagnosticProvider.cs | 2 +- DenizenLangServer/Services/TextDocumentService.cs | 4 ++-- DenizenLangServer/WorkspaceTracker.cs | 9 +++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/DenizenLangServer/DiagnosticProvider.cs b/DenizenLangServer/DiagnosticProvider.cs index d8cb735..5b78a93 100644 --- a/DenizenLangServer/DiagnosticProvider.cs +++ b/DenizenLangServer/DiagnosticProvider.cs @@ -53,7 +53,7 @@ public async void LintCheckLoopThread() needsUpdate = NeedsNewDiag; NeedsNewDiag = false; loops++; - if (loops > 60 && DiagDoc != null && (DiagDoc.Uri.AbsolutePath.EndsWith(".dsc") || DiagDoc.Uri.Scheme == "untitled")) + if (loops > 60 && DiagDoc != null && WorkspaceTracker.IsDenizenDocument(DiagDoc.Uri)) { loops = 0; needsUpdate = true; diff --git a/DenizenLangServer/Services/TextDocumentService.cs b/DenizenLangServer/Services/TextDocumentService.cs index 4069b32..9f237b2 100644 --- a/DenizenLangServer/Services/TextDocumentService.cs +++ b/DenizenLangServer/Services/TextDocumentService.cs @@ -34,7 +34,7 @@ public Hover Hover(TextDocumentIdentifier textDocument, Position position, Cance } // TODO: All this code is a dirty "it works" vertical slice mess that needs to be cleaned up TextDocument doc = GetDocument(textDocument); - if (doc == null || !(textDocument.Uri.AbsolutePath.EndsWith(".dsc") || textDocument.Uri.Scheme == "untitled")) + if (doc == null || !WorkspaceTracker.IsDenizenDocument(textDocument.Uri)) { return null; } @@ -272,7 +272,7 @@ public CompletionList Completion(TextDocumentIdentifier textDocument, Position p return null; } TextDocument doc = GetDocument(textDocument); - if (doc == null || !(textDocument.Uri.AbsolutePath.EndsWith(".dsc") || textDocument.Uri.Scheme == "untitled")) + if (doc == null || !WorkspaceTracker.IsDenizenDocument(textDocument.Uri)) { return new CompletionList(EmptyCompletionItems); } diff --git a/DenizenLangServer/WorkspaceTracker.cs b/DenizenLangServer/WorkspaceTracker.cs index 5d055a3..34c7455 100644 --- a/DenizenLangServer/WorkspaceTracker.cs +++ b/DenizenLangServer/WorkspaceTracker.cs @@ -56,6 +56,15 @@ public static void Replace(Uri file, ScriptChecker checker, string content = nul private static bool HaveShownPath = false; + public static bool IsDenizenDocument(Uri uri) + { + if (uri is null) + { + return false; + } + return uri.AbsolutePath.EndsWith(".dsc") || uri.Scheme == "untitled"; + } + public static string FixPath(Uri uri) { if (uri is null) From bdc8c793cb07f9952ec97e4929233218c9907b02 Mon Sep 17 00:00:00 2001 From: BehrRiley Date: Wed, 1 Jul 2026 04:34:50 -0400 Subject: [PATCH 3/3] pull request review updates - revert `package-lock.json` - fix `UntitledPayloads` memory leak - update `WorkspaceTracker.cs` - reverted unrelated ts edits in `extension.ts` to keep the pull request strictly focused on the unsaved buffers feature --- .../Services/TextDocumentService.cs | 1 + DenizenLangServer/WorkspaceTracker.cs | 2 +- extension/package-lock.json | 4 +-- extension/src/extension.ts | 34 +++++++++---------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/DenizenLangServer/Services/TextDocumentService.cs b/DenizenLangServer/Services/TextDocumentService.cs index 9f237b2..f95ffee 100644 --- a/DenizenLangServer/Services/TextDocumentService.cs +++ b/DenizenLangServer/Services/TextDocumentService.cs @@ -256,6 +256,7 @@ public void WillSave(TextDocumentIdentifier textDocument, TextDocumentSaveReason public void DidClose(TextDocumentIdentifier textDocument) { Session.Documents.TryRemove(textDocument.Uri, out _); + WorkspaceTracker.UntitledPayloads.TryRemove(WorkspaceTracker.FixPath(textDocument.Uri), out _); } private static readonly CompletionItem[] EmptyCompletionItems = []; diff --git a/DenizenLangServer/WorkspaceTracker.cs b/DenizenLangServer/WorkspaceTracker.cs index 34c7455..4c0c906 100644 --- a/DenizenLangServer/WorkspaceTracker.cs +++ b/DenizenLangServer/WorkspaceTracker.cs @@ -45,7 +45,7 @@ public static void Replace(Uri file, ScriptChecker checker, string content = nul { return; } - if (file.Scheme == "untitled" && content != null) + if (file.Scheme == "untitled" && content is not null) { UntitledPayloads[FixPath(file)] = content; } diff --git a/extension/package-lock.json b/extension/package-lock.json index 5ffc108..73e3d52 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "denizenscript", - "version": "1.4.9", + "version": "1.4.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "denizenscript", - "version": "1.4.9", + "version": "1.4.8", "license": "MIT", "dependencies": { "vscode-languageclient": "^7.0.0" diff --git a/extension/src/extension.ts b/extension/src/extension.ts index f7925da..a39c7ab 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -27,7 +27,7 @@ class HighlightCache { let HLCaches : Map = new Map(); function getCache(path : string) { - let result = HLCaches.get(path); + let result : HighlightCache = HLCaches.get(path); if (result) { return result; } @@ -121,19 +121,19 @@ const colorTypes : string[] = [ function loadAllColors() { configuration = vscode.workspace.getConfiguration(); for (const i in colorTypes) { - let str = configuration.get("denizenscript.theme_colors." + colorTypes[i]); + let str : string = configuration.get("denizenscript.theme_colors." + colorTypes[i]); if (str === undefined) { outputChannel.appendLine("Missing color config for " + colorTypes[i]); continue; } colorSet(colorTypes[i], str); } - headerSymbols = configuration.get("denizenscript.header_symbols") ?? ""; - debugHighlighting = configuration.get("denizenscript.debug.highlighting") ?? false; - debugFolding = configuration.get("denizenscript.debug.folding") ?? false; - doInlineColors = configuration.get("denizenscript.behaviors.do_inline_colors") ?? false; - displayDarkColors = configuration.get("denizenscript.behaviors.display_dark_colors") ?? false; - const customColors = configuration.get("denizenscript.theme_colors.text_color_map") ?? ""; + headerSymbols = configuration.get("denizenscript.header_symbols"); + debugHighlighting = configuration.get("denizenscript.debug.highlighting"); + debugFolding = configuration.get("denizenscript.debug.folding"); + doInlineColors = configuration.get("denizenscript.behaviors.do_inline_colors"); + displayDarkColors = configuration.get("denizenscript.behaviors.display_dark_colors"); + const customColors : string = configuration.get("denizenscript.theme_colors.text_color_map"); const colorsSplit : string[] = customColors.split(','); for (const i in colorsSplit) { const color = colorsSplit[i]; @@ -217,7 +217,7 @@ function decorateTag(tag : string, start: number, lineNumber: number, decoration inTagCounter--; if (inTagCounter == 0) { const tagText : string = tag.substring(tagStart + 1, i); - const autoColor = getTagColor(tagText, textColor); + let autoColor : string = getTagColor(tagText, textColor); if (autoColor != null) { addDecor(decorations, "auto:" + autoColor, lineNumber, start + tagStart + 1, start + i); addDecor(decorations, "tag", lineNumber, start + tagStart, start + tagStart + 1); @@ -366,7 +366,7 @@ function isHex(text : string) : boolean { return true; } -function getColorData(color : string) : string | null { +function getColorData(color : string) : string { if (color.startsWith("#")) { return color; } @@ -380,7 +380,7 @@ function getColorData(color : string) : string | null { return null; } -function fixDark(color : string) : string | null { +function fixDark(color : string) { if (color == null) { return null; } @@ -401,7 +401,7 @@ function fixDark(color : string) : string | null { return color; } -function getTagColor(tagText : string, preColor : string) : string | null { +function getTagColor(tagText : string, preColor : string) : string { if (!doInlineColors) { return null; } @@ -417,7 +417,7 @@ function getTagColor(tagText : string, preColor : string) : string | null { } const formatter : string = formatCodes[tagText]; if (formatter) { - const rgb = getColorData(preColor); + const rgb : string = getColorData(preColor); if (rgb) { if (formatter == "bold") { return rgb + "|weight=bold"; @@ -484,7 +484,7 @@ function decorateArg(arg : string, start: number, lineNumber: number, decoration inTagCounter--; if (inTagCounter == 0) { const tagText : string = arg.substring(tagStart + 1, i); - const autoColor = getTagColor(tagText, textColor); + let autoColor : string = getTagColor(tagText, textColor); if (autoColor != null) { addDecor(decorations, "tag", lineNumber, start + tagStart, start + tagStart + 1); addDecor(decorations, "auto:" + autoColor, lineNumber, start + tagStart + 1, start + i); @@ -964,7 +964,7 @@ async function activateDotNet() { try { outputChannel.appendLine("DenizenScript extension attempting to acquire .NET 8"); const requestingExtensionId = 'DenizenScript.denizenscript'; - const result = await vscode.commands.executeCommand('dotnet.acquire', { version: '8.0', requestingExtensionId }); + const result = await vscode.commands.executeCommand('dotnet.acquire', { version: '8.0', requestingExtensionId }); outputChannel.appendLine("DenizenScript extension NET 8 Acquire result: " + result + ": " + result["dotnetPath"]); return result["dotnetPath"]; } @@ -994,7 +994,7 @@ function applyConfigColors() { let color = ""; if (val.startsWith("<") && val.endsWith(">")) { for (const tag of val.slice(1, -1).split("><")) { - const newColor = getTagColor(tag, color); + const newColor : string = getTagColor(tag, color); if (newColor) { color = newColor; } @@ -1070,7 +1070,7 @@ export async function activate(context: vscode.ExtensionContext) { }, null, context.subscriptions); vscode.workspace.onDidChangeTextDocument(event => { if (event.document.languageId === 'denizenscript') { - const curFile = event.document.uri.toString(); + const curFile : string = event.document.uri.toString(); let highlight : HighlightCache = getCache(curFile); event.contentChanges.forEach(change => { if (highlight.needRefreshStartLine == -1 || change.range.start.line < highlight.needRefreshStartLine) {