WIP lsp-manager effort 2

This commit is contained in:
itdominator 2025-07-13 14:41:46 -05:00
parent 9e01628ffb
commit 766e70d766
16 changed files with 308 additions and 102 deletions

View File

@ -100,6 +100,26 @@ const saveFileAs = () => {
});
}
const chooseFolder = () => {
return dialog.showOpenDialog(
{
title: "Choose Folder:",
defaultPath: HOME_DIR,
properties: [
'openDirectory'
]
}
).then((response) => {
if (response.canceled) {
console.debug("Canceled folder selection...");
return "";
}
console.log(response)
return response.filePaths[0];
});
}
const openFiles = (startPath) => {
dialog.showOpenDialog(
{
@ -171,6 +191,7 @@ const closeFile = (fpath) => {
module.exports = {
newtonFs: {
setWindow: setWindow,
chooseFolder: chooseFolder,
openFiles: openFiles,
saveFile: saveFile,
saveFileAs: saveFileAs,

View File

@ -72,7 +72,8 @@ const loadHandlers = () => {
ipcMain.handle('openFiles', (eve, startPath) => newton.fs.openFiles(startPath));
ipcMain.handle('saveFile', (eve, path, content) => newton.fs.saveFile(path, content));
ipcMain.handle('closeFile', (eve, path) => newton.fs.closeFile(path));
ipcMain.handle('saveFileAs', (eve, content) => newton.fs.saveFileAs(content));
ipcMain.handle('saveFileAs', (eve) => newton.fs.saveFileAs());
ipcMain.handle('chooseFolder', (eve) => newton.fs.chooseFolder());
}
app.whenReady().then(async () => {

View File

@ -19,6 +19,7 @@ contextBridge.exposeInMainWorld('fs', {
openFiles: (startPath) => ipcRenderer.invoke("openFiles", startPath),
saveFile: (path, content) => ipcRenderer.invoke("saveFile", path, content),
saveFileAs: () => ipcRenderer.invoke("saveFileAs"),
chooseFolder: () => ipcRenderer.invoke("chooseFolder"),
closeFile: (path) => ipcRenderer.invoke("closeFile", path),
getPathForFile: (file) => webUtils.getPathForFile(file),
onLoadFiles: (callback) => ipcRenderer.on('load-files', (_event, paths) => callback(paths)),

View File

@ -53,6 +53,7 @@
"@angular/platform-browser": "19.2.0",
"ace-builds": "1.43.0",
"ace-diff": "3.0.3",
"ace-layout": "1.5.0",
"ace-linters": "1.7.0",
"bootstrap": "5.3.6",
"bootstrap-icons": "1.12.1",

View File

@ -8,14 +8,15 @@
"command": "lsp-ws-proxy --listen 4114 -- jdtls",
"alt-command": "lsp-ws-proxy -- jdtls",
"alt-command2": "java-language-server",
"socket": "ws://127.0.0.1:4114/?name=jdtls",
"alt-socket": "ws://127.0.0.1:3030/?name=java-language-server",
"socket": "ws://127.0.0.1:4114/java",
"socket-two": "ws://127.0.0.1:4114/?name=jdtls",
"alt-socket": "ws://127.0.0.1:9999/?name=java-language-server",
"initialization-options": {
"bundles": [
"intellicode-core.jar"
],
"workspaceFolders": [
"file://"
"file://{workspace.folder}"
],
"extendedClientCapabilities": {
"classFileContentsSupport": true,
@ -150,7 +151,8 @@
"alt-command": "pylsp",
"alt-command2": "lsp-ws-proxy --listen 4114 -- pylsp",
"alt-command3": "pylsp --ws --port 4114",
"socket": "ws://127.0.0.1:9999/?name=pylsp",
"socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=pylsp",
"initialization-options": {
"pyls": {
"plugins": {
@ -209,13 +211,13 @@
"enabled": true,
"include_class_objects": true,
"include_function_objects": true,
"fuzzy": true
"fuzzy": false
},
"jedi":{
"root_dir": "file://{workspace.folder}",
"extra_paths": [
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
],
"root_dir": ""
]
}
}
}
@ -226,7 +228,8 @@
"info": "https://pypi.org/project/jedi-language-server/",
"command": "jedi-language-server",
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
"socket": "ws://127.0.0.1:3030/?name=jedi-language-server",
"socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
"initialization-options": {
"jediSettings": {
"autoImportModules": [],
@ -255,28 +258,8 @@
"info": "https://clangd.llvm.org/",
"command": "lsp-ws-proxy -- clangd",
"alt-command": "clangd",
"socket": "ws://127.0.0.1:3030/?name=clangd",
"initialization-options": {}
},
"sh": {
"info": "",
"command": "",
"alt-command": "",
"socket": "ws://127.0.0.1:3030/?name=shell",
"initialization-options": {}
},
"go": {
"info": "https://pkg.go.dev/golang.org/x/tools/gopls#section-readme",
"command": "lsp-ws-proxy -- gopls",
"alt-command": "gopls",
"socket": "ws://127.0.0.1:3030/?name=gopls",
"initialization-options": {}
},
"lua": {
"info": "https://github.com/LuaLS/lua-language-server",
"command": "lsp-ws-proxy -- lua-language-server",
"alt-command": "lua-language-server",
"socket": "ws://127.0.0.1:3030/?name=gopls",
"socket": "ws://127.0.0.1:9999/cpp",
"socket-two": "ws://127.0.0.1:9999/?name=clangd",
"initialization-options": {}
},
"c": {
@ -284,7 +267,40 @@
"info": "https://clangd.llvm.org/",
"command": "lsp-ws-proxy -- clangd",
"alt-command": "clangd",
"socket": "ws://127.0.0.1:3030/?name=clangd",
"socket": "ws://127.0.0.1:9999/c",
"socket-two": "ws://127.0.0.1:9999/?name=clangd",
"initialization-options": {}
},
"go": {
"info": "https://pkg.go.dev/golang.org/x/tools/gopls#section-readme",
"command": "lsp-ws-proxy -- gopls",
"alt-command": "gopls",
"socket": "ws://127.0.0.1:9999/go",
"socket-two": "ws://127.0.0.1:9999/?name=gopls",
"initialization-options": {}
},
"typescript": {
"info": "https://github.com/typescript-language-server/typescript-language-server",
"command": "lsp-ws-proxy -- typescript-language-server",
"alt-command": "typescript-language-server --stdio",
"socket": "ws://127.0.0.1:9999/typescript",
"socket-two": "ws://127.0.0.1:9999/?name=ts",
"initialization-options": {}
},
"sh": {
"info": "",
"command": "",
"alt-command": "",
"socket": "ws://127.0.0.1:9999/bash",
"socket-two": "ws://127.0.0.1:9999/?name=shell",
"initialization-options": {}
},
"lua": {
"info": "https://github.com/LuaLS/lua-language-server",
"command": "lsp-ws-proxy -- lua-language-server",
"alt-command": "lua-language-server",
"socket": "ws://127.0.0.1:9999/lua",
"socket-two": "ws://127.0.0.1:9999/?name=lua",
"initialization-options": {}
}
}

View File

@ -31,7 +31,7 @@ export class PaneHandleDirective {
!target.classList.contains("hr-pane-handle") &&
!target.classList.contains("vr-pane-handle")
) {
console.log("Must have 'hr-pane-handle' or 'vr-pane-handle' in classList!");
console.error("Must have 'hr-pane-handle' or 'vr-pane-handle' in classList!");
return;
}

View File

@ -14,8 +14,9 @@ import { ServiceMessage } from '../../../types/service-message.type';
export class LspManagerService {
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
lspConfigData!: {};
languageProviders: {} = {};
workspaceFolder: string = "";
lspConfigDataStr: string = "";
languageProviders: {} = {};
constructor() {
@ -24,69 +25,103 @@ export class LspManagerService {
public loadLspConfigData(): Promise<string | void> {
return this.getLspConfigData().then((lspConfigData: string) => {
this.lspConfigData = JSON.parse(lspConfigData);
if (this.lspConfigData["message"]) {
console.log(
"Warning: LSP this.lspConfigData is a 'message'",
this.lspConfigData
);
this.lspConfigData = {};
}
this.lspConfigDataStr = lspConfigData;
return lspConfigData;
});
}
public registerEditor(editor: any): void {
let modeParts = editor.getSession()["$modeId"].split("/");
let mode = modeParts[ modeParts.length - 1 ];
public registerEditorToLSPClient(editor: any) {
let mode = this.getMode(editor.session);
if ( !this.languageProviders[mode] ) {
this.languageProviders[mode] = this.getLanguageProviderWithClientServer(mode);
if ( this.languageProviders[mode] ) {
this.languageProviders[mode].registerEditor(editor);
return;
}
this.languageProviders[mode].registerEditor(editor);
this.languageProviders[mode]?.registerEditor(editor);
}
private getLspConfigData(): Promise<string> {
return window.fs.getLspConfigData();
}
private getLanguageProviderWithClientServer(mode: string) {
let _initializationOptions = {};
private parseAndReturnLSPConfigData(): {} {
let configData = JSON.parse(
this.lspConfigDataStr.replaceAll("{workspace.folder}", this.workspaceFolder)
);
if ( Object.keys(this.lspConfigData).length !== 0 && this.lspConfigData[mode] ) {
_initializationOptions = this.lspConfigData[mode]["initialization-options"];
if (configData["message"]) {
console.warn(
"Warning: LSP this.lspConfigDataStr is a 'message'",
this.lspConfigDataStr
);
configData = {};
}
let servers: LanguageClientConfig[] = [
{
module: () => import("ace-linters/build/language-client"),
modes: mode,
type: "socket",
socket: new WebSocket(`ws://127.0.0.1:9999/${mode}`),
// socket: new WebSocket("ws://127.0.0.1:9999/?name=pylsp"),
initializationOptions: _initializationOptions
}
];
return AceLanguageClient.for(servers);
return configData;
}
private getLanguageProviderWithWebWorker() {
private getInitializationOptions(mode: string, configData: {}): {} {
let _initializationOptions = {};
if ( Object.keys(configData).length !== 0 && configData[mode] ) {
_initializationOptions = configData[mode]["initialization-options"];
}
return _initializationOptions;
}
public createLanguageProviderWithClientServer(mode: string): LanguageProvider {
if ( this.languageProviders[mode] ) return;
let servers: LanguageClientConfig[] = [];
try {
let configData = this.parseAndReturnLSPConfigData();
let _initializationOptions = this.getInitializationOptions(mode, configData);
servers = [
{
module: () => import("ace-linters/build/language-client"),
modes: mode,
type: "socket",
socket: new WebSocket( configData[mode]["socket"] ),
initializationOptions: _initializationOptions
}
];
} catch(error) {
console.error(
"Error: Language Server could not be loaded OR doesn't exist in Newton-LSP config setup...",
);
return;
}
this.languageProviders[mode] = AceLanguageClient.for(servers);
// this.languageProviders[mode].requireFilePath = true;
this.languageProviders[mode].changeWorkspaceFolder(this.workspaceFolder);
return this.languageProviders[mode];
}
private getLanguageProviderWithWebWorker(): LanguageProvider {
let worker = new Worker(new URL('./webworker.js', import.meta.url));
return LanguageProvider.create(worker);
}
protected setSessionFilePath(session: any, mode: string = "", filePath: string = "") {
if ( !session || !mode || !filePath || !this.languageProviders[mode] ) return;
public setSessionFilePath(session: any, filePath: string = "") {
if ( !session || !filePath ) return;
let mode = this.getMode(session);
if ( !this.languageProviders[mode] ) return;
this.languageProviders[mode].setSessionFilePath(session, filePath);
}
protected closeDocument(session: any, mode: string) {
if ( !session || !mode || !this.languageProviders[mode] ) return;
public getMode(session: any): string {
return session.getMode()["$id"].replace("ace/mode/", "");
}
public closeDocument(session: any) {
if ( !session ) return;
let mode = this.getMode(session);
if ( !this.languageProviders[mode] ) return;
this.languageProviders[mode].closeDocument(session);
}

View File

@ -89,13 +89,14 @@ export class FilesService {
if (loadFileContents)
data = await window.fs.getFileContents(file.path);
file.session = new EditSession(data);
file.session = new EditSession(data);
file.session["id"] = path;
file.session.setUndoManager( new UndoManager() );
file.session.setMode( getModeForPath( file.path ).mode );
this.files.set(file.path, file);
} catch (error) {
console.log(
console.error(
`---- Error ----\nPath: ${path}\nMessage: ${error}`
);
}

View File

@ -2,4 +2,4 @@ export abstract class EditorType {
static MiniMap: string = "mini-map";
static ReadOnly: string = "read-only";
static Standalone: string = "standalone";
}
}

View File

@ -2,6 +2,10 @@ import { Component } from "@angular/core";
// Import Ace and its modes/themes so that `ace` global is defined
import * as ace from "ace-builds/src-min-noconflict/ace";
// Note: https://github.com/mkslanc/ace-linters/blob/c286d85c558530aa1b0597d02108bc782abd4736/packages/demo/file-api-websockets/client.ts#L27
// import { AceLayout, Box, TabManager, Button, dom, AceTreeWrapper, FileSystemWeb, Pane, AceEditor, Tab } from "ace-layout";
import "ace-builds/src-min-noconflict/ext-settings_menu";
import "ace-builds/src-min-noconflict/ext-keybinding_menu";
import "ace-builds/src-min-noconflict/ext-command_bar";
@ -59,8 +63,8 @@ export class CodeViewComponent extends CodeViewBase {
this.editor = ace.edit( this.editorElm.nativeElement );
this.editor.setOptions( this.editorSettings.CONFIG );
this.editorsService.set(this.uuid, this);
if (this.isDefault) {
this.editorsService.set(this.uuid, this);
this.editorsService.setActiveEditor(this.uuid);
this.addActiveStyling();
this.editor.focus();
@ -128,11 +132,11 @@ export class CodeViewComponent extends CodeViewBase {
this.editorsService.sendMessage(message);
this.searchReplaceService.sendMessage(message);
this.editorsService.sendMessage(message);
message = new ServiceMessage();
message.action = "set-active-editor";
message.rawData = this;
this.lspManagerService.sendMessage(message);
this.markdownPreviewService.sendMessage(message);
this.updateInfoBar();
@ -175,6 +179,12 @@ export class CodeViewComponent extends CodeViewBase {
});
this.editor.on("changeSession", (session) => {
let message = new ServiceMessage();
message.action = "editor-update";
message.rawData = this;
this.lspManagerService.sendMessage(message);
this.updateInfoBar();
});
}

View File

@ -139,6 +139,7 @@ export class EditorsComponent {
}
}
activeComponent.lspManagerService.closeDocument(file.session);
this.filesService.delete(file);
}
@ -192,7 +193,7 @@ export class EditorsComponent {
});
window.fs.onUpdateFilePath(async (path: string) => {
console.log(path);
console.log("TODO (onUpdateFilePath) :", path);
// this.tabsService.sendMessage(message);
// this.filesService.sendMessage(message);
});

View File

@ -1,4 +1,11 @@
.lsp-config-text {
display: grid;
min-height: 25em;
}
}
.clear-left-padding {
padding-left: 0px;
}
.clear-right-padding {
padding-right: 0px;
}

View File

@ -2,24 +2,63 @@
<div class="row mt-2 mb-3">
<div class="col">
<div class="col clear-right-padding">
<div class="input-group-sm">
<input class="form-control" placeholder="Project Path..." />
<label class="form-control" [innerText]="lspManagerService.workspaceFolder || 'Project Path...'">
</label>
</div>
</div>
<div class="col col-auto clear-left-padding">
<button class="btn btn-sm btn-dark" (click)="clearWorkspaceFolder()">x</button>
</div>
<div class="col col-auto">
<div class="input-group-sm">
<button class="btn btn-sm btn-dark">Choose Directory</button>
<button class="btn btn-sm btn-dark" (click)="setWorkspaceFolder()">Workspace Folder</button>
<button class="btn btn-sm btn-danger ms-5" (click)="hideLspManager()">X</button>
</div>
</div>
</div>
<div class="row mt-2 md-2">
<div class="col">
LSP Configs:
</div>
</div>
<div class="row">
<div class="col">
<code-view #lspEditorComponent [mode]="'standalone'" class="lsp-config-text"></code-view>
</div>
</div>
<div class="row mt-2 md-2">
<div class="col col-sm" [hidden]="!lspManagerService.workspaceFolder">
<button class="btn btn-sm btn-dark" (click)="createLanguageClient()">Create Language Client</button>
</div>
<div class="col">
Target Editor: <label [innerText]="editor?.id || '...'"></label>
</div>
<div class="col-sm" [hidden]="!lspManagerService.workspaceFolder">
<button class="btn btn-sm btn-dark"
(click)="registerEditorToLanguageClient()">
Register Editor To LSP
</button>
</div>
<div class="col">
Active Editor Session:
</div>
</div>
<div class="row">
<div class="col">
<code-view #editorComponent [mode]="'standalone'" class="lsp-config-text"></code-view>
<code-view #sessionEditorComponent [mode]="'read-only'" class="lsp-config-text"></code-view>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { Component, ElementRef, HostBinding, ViewChild, inject } from '@angular/core';
import { Component, ChangeDetectorRef, ElementRef, HostBinding, ViewChild, inject } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { LspManagerService } from '../../common/services/editor/lsp-manager/lsp-manager.service';
@ -23,14 +23,20 @@ import { ServiceMessage } from '../../common/types/service-message.type';
}
})
export class LspManagerComponent {
private unsubscribe: Subject<void> = new Subject();
private unsubscribe: Subject<void> = new Subject();
private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef);
private lspManagerService: LspManagerService = inject(LspManagerService);
lspManagerService: LspManagerService = inject(LspManagerService);
@HostBinding("class.hidden") isHidden: boolean = true;
@ViewChild('editorComponent') editorComponent!: CodeViewComponent;
lspTextEditor!: any;
private editor: any;
@ViewChild('lspEditorComponent') lspEditorComponent!: CodeViewComponent;
@ViewChild('sessionEditorComponent') sessionEditorComponent!: CodeViewComponent;
lspTextEditor: any;
innerEditor: any;
editor: any;
activeFile: any;
constructor() {
@ -38,13 +44,7 @@ export class LspManagerComponent {
private ngAfterViewInit(): void {
this.lspTextEditor = this.editorComponent.editor;
this.lspManagerService.loadLspConfigData().then((lspConfigData) => {
this.lspTextEditor.session.setMode("ace/mode/json");
this.lspTextEditor.session.setValue(lspConfigData);
});
this.mapEditorsAndLoadConfig();
this.loadSubscribers();
}
@ -53,6 +53,16 @@ export class LspManagerComponent {
this.unsubscribe.complete();
}
private mapEditorsAndLoadConfig() {
this.lspTextEditor = this.lspEditorComponent.editor;
this.innerEditor = this.sessionEditorComponent.editor;
this.lspManagerService.loadLspConfigData().then((lspConfigData) => {
this.lspTextEditor.session.setMode("ace/mode/json");
this.lspTextEditor.session.setValue(lspConfigData);
});
}
private loadSubscribers() {
this.lspManagerService.getMessage$().pipe(
takeUntil(this.unsubscribe)
@ -61,28 +71,90 @@ export class LspManagerComponent {
this.toggleLspManager(message);
} else if (message.action === "set-active-editor") {
this.setActiveEditor(message);
} else if (message.action === "editor-update") {
this.editorUpdate(message);
} else if (message.action === "close-file") {
this.closeFile(message);
}
});
}
public hideLspManager() {
this.isHidden = true;
this.editor.focus();
public clearWorkspaceFolder() {
this.lspManagerService.workspaceFolder = "";
}
public setWorkspaceFolder() {
window.fs.chooseFolder().then((folder: string) => {
if (!folder) return;
this.lspManagerService.workspaceFolder = folder;
});
}
public createLanguageClient() {
let mode = this.lspManagerService.getMode(this.editor.session);
this.lspManagerService.createLanguageProviderWithClientServer(mode);
}
public registerEditorToLanguageClient() {
this.lspManagerService.registerEditorToLSPClient(this.editor);
/*
this.lspManagerService.setSessionFilePath(
this.editor.session,
this.activeFile.path
);
*/
}
public globalLspManagerKeyHandler(event: any) {
if (event.ctrlKey && event.shiftKey && event.key === "l") {
this.hideLspManager();
}
}
public hideLspManager() {
this.isHidden = true;
this.editor.focus();
}
private toggleLspManager(message: ServiceMessage) {
this.isHidden = !this.isHidden;
if (this.isHidden) return;
// Note: hack for issue with setActiveEditor TODO
setTimeout(() => {
this.innerEditor.setSession(this.editor.getSession());
}, 10);
}
private setActiveEditor(message: ServiceMessage) {
this.editor = message.rawData;
this.editor = message.rawData.editor;
this.activeFile = message.rawData.activeFile;
// TODO: figure out why this doesn't update the session consistently...
// It seems maybe bound to visible state as change detector ref didn't help either.
// this.innerEditor.setSession(this.editor.session);
}
private editorUpdate(message: ServiceMessage) {
if (!message.rawData.activeFile) return;
this.editor.setSession(message.rawData.editor.getSession())
this.activeFile = message.rawData.activeFile;
/*
this.lspManagerService.setSessionFilePath(
this.editor.session,
this.activeFile.path
);
*/
}
private closeFile(message: ServiceMessage) {
this.lspManagerService.closeDocument(message.rawData);
}
}

View File

@ -61,7 +61,7 @@ export class MarkdownPreviewComponent {
setTimeout(() => {
this.updatePreview();
}, 200);
}, 10);
}
private setActiveEditor(message: ServiceMessage) {

View File

@ -29,6 +29,7 @@ declare global {
openFiles: (arg0) => Promise<string>,
saveFile: (arg0: any, arg1: any) => Promise<string>,
saveFileAs: () => Promise<string>,
chooseFolder: () => Promise<string>,
closeFile: (arg0: any) => Promise<string>,
getPathForFile: any,
onLoadFiles: (arg0: any) => Promise<string>,