diff --git a/client/src/commands.ts b/client/src/commands.ts index 9c7ad8d6..fead7252 100644 --- a/client/src/commands.ts +++ b/client/src/commands.ts @@ -72,22 +72,24 @@ const DYNAMIC_DIAGRAM_URI = 'http://localhost:3000/' export const startRepl = (): Task => { const currentDocument = window.activeTextEditor?.document const wollokLSPConfiguration = workspace.getConfiguration(wollokLSPExtensionCode) - const dynamicDiagramDarkMode = wollokLSPConfiguration.get('dynamicDiagramDarkMode') ?? false - const cliCommands = [`repl`, ...getFiles(currentDocument), '--skipValidations', dynamicDiagramDarkMode ? '--darkMode' : ''] + const dynamicDiagramDarkMode = wollokLSPConfiguration.get('dynamicDiagram.dynamicDiagramDarkMode') as boolean + const openDynamicDiagram = wollokLSPConfiguration.get('dynamicDiagram.openDynamicDiagramOnRepl') as boolean + const millisecondsToOpenDynamicDiagram = wollokLSPConfiguration.get('dynamicDiagram.millisecondsToOpenDynamicDiagram') as number + + const cliCommands = [`repl`, ...getFiles(currentDocument), '--skipValidations', dynamicDiagramDarkMode ? '--darkMode' : '', openDynamicDiagram ? '': '--skipDiagram'] // Terminate previous tasks vscode.commands.executeCommand('workbench.action.terminal.killAll') const replTask = wollokCLITask('repl', `Wollok Repl: ${getCurrentFileName(currentDocument)}`, cliCommands) - const openDynamicDiagram = wollokLSPConfiguration.get('openDynamicDiagramOnRepl') as boolean if (openDynamicDiagram) { setTimeout(() => { - const openInternalDynamicDiagram = wollokLSPConfiguration.get('openInternalDynamicDiagram') as boolean + const openInternalDynamicDiagram = wollokLSPConfiguration.get('dynamicDiagram.openInternalDynamicDiagram') as boolean if (openInternalDynamicDiagram) { vscode.commands.executeCommand('simpleBrowser.show', DYNAMIC_DIAGRAM_URI) } else { vscode.env.openExternal(vscode.Uri.parse(DYNAMIC_DIAGRAM_URI)) } - }, 1000) + }, millisecondsToOpenDynamicDiagram) } return replTask } diff --git a/client/src/extension.ts b/client/src/extension.ts index 66032861..1bc060b3 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -81,8 +81,10 @@ export function activate(context: ExtensionContext): void { }) // Force environment to restart - const revalidateWorskpace = (_event) => - client.sendRequest('STRONG_FILES_CHANGED').then(validateWorkspace) + const revalidateWorskpace = (_event) => { + const pathForChange = (file) => file.oldUri?.fsPath ?? file.fsPath + return client.sendRequest(`STRONG_FILES_CHANGED:${_event.files.map(pathForChange).join(',')}`).then(validateWorkspace) + } workspace.onDidDeleteFiles(revalidateWorskpace) workspace.onDidRenameFiles(revalidateWorskpace) diff --git a/client/src/test/commands.test.ts b/client/src/test/commands.test.ts index 40f6b802..3b585b47 100644 --- a/client/src/test/commands.test.ts +++ b/client/src/test/commands.test.ts @@ -99,7 +99,7 @@ suite('Should run commands', () => { startRepl, ` repl ${toPosix( pepitaURI.fsPath, - )} --skipValidations --darkMode -p ${expectedPathByShell( + )} --skipValidations --darkMode -p ${expectedPathByShell( 'bash', folderURI.fsPath, )}`, diff --git a/client/src/test/helper.ts b/client/src/test/helper.ts index 17ff4677..0a6bfd61 100644 --- a/client/src/test/helper.ts +++ b/client/src/test/helper.ts @@ -30,9 +30,9 @@ export async function activate(docUri: Uri, timeToWait = 2000): Promise { document = await workspace.openTextDocument(docUri) editor = await window.showTextDocument(document) await sleep(timeToWait) // Wait for server activation - } catch (e) { - console.error(e) - throw e + } catch (error) { + console.error(error) + throw error } } @@ -43,9 +43,11 @@ async function sleep(milliseconds: number) { export const getDocumentPath = (docPath: string): string => { return path.resolve(__dirname, path.join('..', '..', 'testFixture'), docPath) } + export const getDocumentURI = (docPath: string): Uri => { return Uri.file(getDocumentPath(docPath)) } + export const getFolderURI = (): Uri => { return Uri.file(getDocumentPath('')) } diff --git a/client/src/test/hover.test.ts b/client/src/test/hover.test.ts index 27701436..c6143611 100644 --- a/client/src/test/hover.test.ts +++ b/client/src/test/hover.test.ts @@ -43,6 +43,8 @@ suite('Should display on hover', () => { async function testHover(uri: Uri, position: Position, expected: any): Promise { await activate(uri) const actual = await commands.executeCommand('vscode.executeHoverProvider', uri, position) + delete actual[0]['canDecreaseHover'] + delete actual[0]['canIncreaseHover'] assert.deepEqual(actual, [expected]) assert.deepEqual( actual[0].contents.map(content => content.value), diff --git a/client/src/test/runTest.ts b/client/src/test/runTest.ts index 02539da1..d675eca7 100644 --- a/client/src/test/runTest.ts +++ b/client/src/test/runTest.ts @@ -27,7 +27,7 @@ async function main() { ], }) } catch (err) { - console.error('Failed to run tests', err) + console.error('✘ Failed to run tests', err) process.exit(1) } } diff --git a/images/wollokFile.png b/images/wollokFile.png new file mode 100644 index 00000000..c3dcc779 Binary files /dev/null and b/images/wollokFile.png differ diff --git a/package.json b/package.json index bf438a85..5b1228a4 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,8 @@ { "id": "wollok", "icon": { - "dark": "./images/wollok.png", - "light": "./images/wollok.png" + "dark": "./images/wollokFile.png", + "light": "./images/wollokFile.png" }, "aliases": [ "Wollok", @@ -66,19 +66,21 @@ "scope": "resource", "type": "boolean", "description": "Abbreviate assignments", - "default": true + "default": true, + "order": 0 }, "wollokLSP.formatter.maxWidth": { "scope": "resource", "type": "number", "description": "Maximum width allowed in a line", - "default": 80 + "default": 80, + "order": 1 }, "wollokLSP.cli-path": { "scope": "resource", "type": "string", "description": "Path to Wollok-CLI.", - "order": 0 + "order": 10 }, "wollokLSP.language": { "scope": "resource", @@ -90,14 +92,14 @@ ], "default": "Based on Local Environment", "description": "Language used while reporting linter errors and warnings.", - "order": 1 + "order": 11 }, "wollokLSP.maxNumberOfProblems": { "scope": "resource", "type": "number", "default": 100, "description": "Controls the maximum number of problems produced by the server.", - "order": 2 + "order": 12 }, "wollokLSP.trace.server": { "scope": "window", @@ -109,41 +111,49 @@ ], "default": "off", "description": "Traces the communication between VS Code and the language server.", - "order": 3 + "order": 20 }, - "wollokLSP.openDynamicDiagramOnRepl": { + "wollokLSP.dynamicDiagram.openDynamicDiagramOnRepl": { "scope": "resource", "type": "boolean", "default": true, "description": "Opens the dynamic diagram when running the REPL.", - "order": 4 + "order": 30 }, - "wollokLSP.openInternalDynamicDiagram": { + "wollokLSP.dynamicDiagram.openInternalDynamicDiagram": { "scope": "resource", "type": "boolean", "default": true, "description": "If true, opens an internal dynamic diagram inside Wollok IDE. If false, it will open a new external browser.", - "order": 5 + "order": 31 + }, + "wollokLSP.dynamicDiagram.millisecondsToOpenDynamicDiagram": { + "scope": "resource", + "type": "number", + "default": 1000, + "description": "Milliseconds we wait until we open Dynamic Diagram in Browser.", + "order": 32 }, - "wollokLSP.dynamicDiagramDarkMode": { + "wollokLSP.dynamicDiagram.dynamicDiagramDarkMode": { "scope": "resource", "type": "boolean", "default": true, "description": "If true, opens dynamic diagram in Dark Mode. Otherwise, it uses Light Mode.", - "order": 6 + "order": 33 }, "wollokLSP.maxThreshold": { "scope": "resource", "type": "number", "default": 100, "description": "Maximum threshold in milliseconds: if an operation takes longer, it will be saved in the log file.", - "order": 7 + "order": 40 }, "wollokLSP.typeSystem.enabled": { "scope": "resource", "type": "boolean", "description": "Enable Type System (experimental)", - "default": false + "default": false, + "order": 50 } } }, @@ -193,7 +203,7 @@ "lint-staged": "lint-staged" }, "dependencies": { - "wollok-ts": "4.1.0" + "wollok-ts": "4.1.1" }, "devDependencies": { "@types/expect": "^24.3.0", diff --git a/server/src/functionalities/autocomplete/autocomplete.ts b/server/src/functionalities/autocomplete/autocomplete.ts index 3045b258..9aac56dd 100644 --- a/server/src/functionalities/autocomplete/autocomplete.ts +++ b/server/src/functionalities/autocomplete/autocomplete.ts @@ -2,7 +2,7 @@ import { CompletionItem, CompletionItemKind, CompletionParams, InsertTextFormat, import { Class, Entity, Field, Method, Mixin, Module, Name, Node, OBJECT_MODULE, Parameter, Reference, Singleton, Environment, Import, parentModule, getAllUninitializedAttributes } from 'wollok-ts' import { TimeMeasurer } from '../../time-measurer' import { cursorNode, relativeFilePath, packageToURI } from '../../utils/text-documents' -import { isImportedIn } from '../../utils/vm/wollok' +import { isNotImportedIn } from 'wollok-ts' import { completionsForNode } from './node-completion' import { completeMessages } from './send-completion' import { match, when } from 'wollok-ts/dist/extensions' @@ -46,7 +46,7 @@ export const withImport = (mapper: CompletionItemMapper) => ( if( importedPackage && originalPackage && - isImportedIn(importedPackage, originalPackage) + isNotImportedIn(importedPackage, originalPackage) ) { result.detail = `Add import ${importedPackage.fileName ? relativeFilePath(packageToURI(importedPackage)) : importedPackage.name}${result.detail ? ` - ${result.detail}` : ''}` result.additionalTextEdits = (result.additionalTextEdits ?? []).concat( diff --git a/server/src/functionalities/autocomplete/node-completion.ts b/server/src/functionalities/autocomplete/node-completion.ts index bc91c305..59d34823 100644 --- a/server/src/functionalities/autocomplete/node-completion.ts +++ b/server/src/functionalities/autocomplete/node-completion.ts @@ -2,6 +2,7 @@ import { CompletionItem } from 'vscode-languageserver' import { Node, Body, Method, Singleton, Module, Environment, Package, Class, Mixin, Describe, Program, Test, Reference, New, Import, Entity, implicitImport, is, parentImport, match, when } from 'wollok-ts' import { classCompletionItem, fieldCompletionItem, initializerCompletionItem, parameterCompletionItem, singletonCompletionItem, entityCompletionItem, withImport } from './autocomplete' import { optionModules, optionImports, optionDescribes, optionTests, optionReferences, optionMethods, optionPrograms, optionAsserts, optionConstReferences, optionInitialize, optionPropertiesAndReferences } from './options-autocomplete' +import { logger } from '../../utils/logger' export const completionsForNode = (node: Node): CompletionItem[] => { try { @@ -19,7 +20,8 @@ export const completionsForNode = (node: Node): CompletionItem[] => { when(Reference)(completeReference), when(New)(completeNew) ) - } catch { + } catch (error) { + logger.error(`✘ Completions for node failed: ${error}`, error) return completeForParent(node) } } diff --git a/server/src/functionalities/definition.ts b/server/src/functionalities/definition.ts index 7610a6cd..7e0ed3bd 100644 --- a/server/src/functionalities/definition.ts +++ b/server/src/functionalities/definition.ts @@ -1,76 +1,45 @@ import { Location, TextDocumentPositionParams } from 'vscode-languageserver' -import { Environment, Method, Module, New, Node, Reference, Self, Send, Singleton, Super, is, match, when } from 'wollok-ts' +import { Environment, Method, Module, Node, Reference, Self, Send, Super, is, match, sendDefinitions, when } from 'wollok-ts' import { getNodesByPosition, nodeToLocation } from '../utils/text-documents' +import { logger } from '../utils/logger' export const definition = (environment: Environment) => ( textDocumentPosition: TextDocumentPositionParams ): Location[] => { const cursorNodes = getNodesByPosition(environment, textDocumentPosition) - const definitions = getNodeDefinition(environment)(cursorNodes.reverse()[0]) + const definitions = getDefinition(environment)(cursorNodes.reverse()[0]) return definitions.map(nodeToLocation) } -// WOLLOK-TS: hablar con Nahue/Ivo, para mí desde acá para abajo todo se podria migrar a wollok-ts -export const getNodeDefinition = (environment: Environment) => (node: Node): Node[] => { +export const getDefinition = (environment: Environment) => (node: Node): Node[] => { try { - return match(node)( - when(Reference)(node => definedOrEmpty(referenceDefinition(node))), - when(Send)(sendDefinitions(environment)), - when(Super)(node => definedOrEmpty(superMethodDefinition(node))), - when(Self)(node => definedOrEmpty(node.ancestors.find(is(Module)))) - ) - } catch { + return getNodeDefinition(environment)(node) + } catch (error) { + logger.error(`✘ Error in getDefinition: ${error}`, error) return [node] } } -function referenceDefinition(ref: Reference): Node | undefined { - return ref.target -} - - -const sendDefinitions = (environment: Environment) => (send: Send): Method[] => { +// TODO: terminar de migrar a wollok-ts estas 4 definiciones +export const getNodeDefinition = (environment: Environment) => (node: Node): Node[] => { try { - return match(send.receiver)( - when(Reference)(node => { - const target = node.target - return target && is(Singleton)(target) ? - definedOrEmpty(target.lookupMethod(send.message, send.args.length)) - : allMethodDefinitions(environment, send) - }), - when(New)(node => definedOrEmpty(node.instantiated.target?.lookupMethod(send.message, send.args.length))), - when(Self)(_ => moduleFinderWithBackup(environment, send)( - (module) => definedOrEmpty(module.lookupMethod(send.message, send.args.length)) - )), + return match(node)( + when(Reference)(node => definedOrEmpty(node.target)), + when(Send)(sendDefinitions(environment)), + when(Super)(node => definedOrEmpty(superMethodDefinition(node))), + when(Self)(node => definedOrEmpty(getParentModule(node))) ) } catch { - return allMethodDefinitions(environment, send) + return [node] } } -function superMethodDefinition(superNode: Super): Method | undefined { +const superMethodDefinition = (superNode: Super): Method | undefined => { const currentMethod = superNode.ancestors.find(is(Method))! - const module = superNode.ancestors.find(is(Module)) + const module = getParentModule(superNode) return module ? module.lookupMethod(currentMethod.name, superNode.args.length, { lookupStartFQN: module.fullyQualifiedName }) : undefined } -function allMethodDefinitions(environment: Environment, send: Send): Method[] { - const arity = send.args.length - const name = send.message - return environment.descendants.filter(n => - is(Method)(n) && - n.name === name && - n.parameters.length === arity - ) as Method[] -} - - -// UTILS -const moduleFinderWithBackup = (environment: Environment, send: Send) => (methodFinder: (module: Module) => Method[]) => { - const module = send.ancestors.find(is(Module)) - return module ? methodFinder(module) : allMethodDefinitions(environment, send) -} +const getParentModule = (node: Node) => node.ancestors.find(is(Module)) -function definedOrEmpty(value: T | undefined): T[] { - return value ? [value] : [] -} \ No newline at end of file +const definedOrEmpty = (value: T | undefined): T[] => value ? [value] : [] \ No newline at end of file diff --git a/server/src/functionalities/formatter.ts b/server/src/functionalities/formatter.ts index 8cad7cc2..0b6fb709 100644 --- a/server/src/functionalities/formatter.ts +++ b/server/src/functionalities/formatter.ts @@ -23,12 +23,12 @@ export const formatDocument = (environment: Environment, { formatter: formatterC }) ), ] - } catch(err) { - let message = `Could not format file '${file.fileName}'` - if(err instanceof PrintingMalformedNodeError){ - message += `: ${err.message} {${err.node.toString()}}` + } catch(error) { + let message = `✘ Could not format file '${file.fileName}'` + if (error instanceof PrintingMalformedNodeError) { + message += `: ${error.message} {${error.node.toString()}}` } - logger.error(message) + logger.error(message, error) return null } } diff --git a/server/src/functionalities/hover.ts b/server/src/functionalities/hover.ts index 413863db..2045486c 100644 --- a/server/src/functionalities/hover.ts +++ b/server/src/functionalities/hover.ts @@ -30,8 +30,8 @@ export const typeDescriptionOnHover = (environment: Environment, { typeSystem }: ], range: node.sourceMap ? toVSCRange(node.sourceMap) : undefined, } - } catch (e) { - logger.error('Failed to get type description', e) + } catch (error) { + logger.error(`✘ Failed to get type description: ${error}`, error) return null } } \ No newline at end of file diff --git a/server/src/functionalities/references.ts b/server/src/functionalities/references.ts index b968dda8..8080ef6e 100644 --- a/server/src/functionalities/references.ts +++ b/server/src/functionalities/references.ts @@ -1,7 +1,6 @@ import { Location, ReferenceParams } from 'vscode-languageserver' -import { Environment, Method, Node, Reference, Send, Singleton } from 'wollok-ts' +import { Environment, Method, mayExecute, targettingAt } from 'wollok-ts' import { cursorNode, nodeToLocation } from '../utils/text-documents' -import { targettingAt } from 'wollok-ts' export const references = (environment: Environment) => (params: ReferenceParams): Location[] | null => { const node = cursorNode(environment, params.position, params.textDocument) @@ -12,9 +11,3 @@ export const references = (environment: Environment) => (params: ReferenceParams targettingAt(node) ).map(nodeToLocation) } - -const mayExecute = (method: Method) => (aNode: Node) => - aNode.is(Send) && - aNode.message === method.name && - // exclude cases where a message is sent to a different singleton - !(aNode.receiver.is(Reference) && aNode.receiver.target?.is(Singleton) && aNode.receiver.target !== method.parent) \ No newline at end of file diff --git a/server/src/functionalities/rename.ts b/server/src/functionalities/rename.ts index c5fee06d..6cd25f2a 100644 --- a/server/src/functionalities/rename.ts +++ b/server/src/functionalities/rename.ts @@ -17,8 +17,8 @@ export const rename = (documents: TextDocuments) => (environment: export const requestIsRenamable = (environment: Environment) => (params: RenameParams): any => { const renamedNode = cursorNode(environment, params.position, params.textDocument) - if(!renamedNode) return null - if( renamedNode.is(Reference) && renamedNode.target && isRenamable(renamedNode.target) || isRenamable(renamedNode)) { + if (!renamedNode) return null + if (renamedNode.is(Reference) && renamedNode.target && isRenamable(renamedNode.target) || isRenamable(renamedNode)) { // ToDo: switch back to defaultBehavior when https://github.com/microsoft/vscode/issues/198423 is released return { range: toVSCRange(renamedNode.sourceMap!), @@ -38,7 +38,7 @@ function renameNode(node: Renamable, newName: string, environment: Environment, const hits: (Renamable | Reference)[] = [node] const referencesRenamedNode = targettingAt(node) environment.forEach(aNode => { - if(!aNode.isSynthetic && referencesRenamedNode(aNode)) { + if (!aNode.isSynthetic && referencesRenamedNode(aNode)) { hits.push(aNode as Reference) } }) diff --git a/server/src/functionalities/symbols.ts b/server/src/functionalities/symbols.ts index bcd05bc3..37b09f12 100644 --- a/server/src/functionalities/symbols.ts +++ b/server/src/functionalities/symbols.ts @@ -1,15 +1,14 @@ import { DocumentSymbol, DocumentSymbolParams, SymbolKind, WorkspaceSymbol, WorkspaceSymbolParams } from 'vscode-languageserver' -import { Environment, Field, Method, Module, Node, Package, Program, Test, Variable } from 'wollok-ts' +import { Environment, Field, Method, Module, Node, Package, projectPackages, Program, Test, Variable } from 'wollok-ts' import { logger } from '../utils/logger' import { packageFromURI, toVSCRange, uriFromRelativeFilePath } from '../utils/text-documents' -import { projectPackages } from '../utils/vm/wollok' type Symbolyzable = Program | Test | Module | Variable | Field | Method | Test export const documentSymbols = (environment: Environment) => (params: DocumentSymbolParams): DocumentSymbol[] => { const document = packageFromURI(params.textDocument.uri, environment) if (!document){ - logger.error('Could not produce symbols: document not found') + logger.error(`✘ Could not produce symbols: document ${params.textDocument.uri} not found`) return [] } return documentSymbolsFor(document) diff --git a/server/src/linter.ts b/server/src/linter.ts index 0e03c343..01a54141 100644 --- a/server/src/linter.ts +++ b/server/src/linter.ts @@ -4,15 +4,12 @@ import { DiagnosticSeverity, } from 'vscode-languageserver' import { TextDocument } from 'vscode-languageserver-textdocument' -import { Environment, Problem, validate } from 'wollok-ts' -import { List } from 'wollok-ts/dist/extensions' +import { Environment, List, Problem, validate } from 'wollok-ts' import { reportValidationMessage } from './functionalities/reporter' import { updateDocumentSettings } from './settings' import { TimeMeasurer } from './time-measurer' -import { - isNodeURI, relativeFilePath, - trimIn, -} from './utils/text-documents' +import { isNodeURI, relativeFilePath, trimIn } from './utils/text-documents' +import { logger } from './utils/logger' // ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════ // INTERNAL FUNCTIONS @@ -63,15 +60,17 @@ export const validateTextDocument = async (environment: Environment): Promise => { await updateDocumentSettings(connection) + const timeMeasurer = new TimeMeasurer() try { - const documentUri = relativeFilePath(textDocument.uri) - const timeMeasurer = new TimeMeasurer() const problems = validate(environment) sendDiagnostics(connection, problems, allDocuments) + } catch (error) { + logger.error(`✘ Validate text document error: ${error}`, error) + generateErrorForFile(connection, textDocument) + } finally { + const documentUri = relativeFilePath(textDocument.uri) timeMeasurer.addTime(`Validating ${documentUri}`) timeMeasurer.finalReport() - } catch (e) { - generateErrorForFile(connection, textDocument) } } diff --git a/server/src/server.ts b/server/src/server.ts index 17e0d819..4412cd8f 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -59,34 +59,6 @@ const requestContext = combineLatest([environmentProvider.$environment.pipe(filt const requestProgressReporter = new ProgressReporter(connection, { identifier: 'wollok-request', title: 'Processing Request...' }) -function syncHandler(requestHandler: ServerRequestHandler): ServerRequestHandler { - return (params, cancel, workDoneProgress, resultProgress) => { - requestProgressReporter.begin() - try { - return requestHandler(params, cancel, workDoneProgress, resultProgress) - } catch (e) { - logger.error('✘ Failed to process request', e) - return null - } finally { - requestProgressReporter.end() - } - - } -} - -function waitForFirstHandler(requestHandler: (environment: Environment, settings: ClientConfigurations) => ServerRequestHandler): ServerRequestHandler { - return (params, cancel, workDoneProgress, resultProgress) => { - requestProgressReporter.begin() - return new Promise(resolve => { - firstValueFrom(requestContext).then(([newEnvironment, newSettings]) => { - const result = syncHandler(requestHandler(newEnvironment!, newSettings))(params, cancel, workDoneProgress, resultProgress) - requestProgressReporter.end() - resolve(result) - }) - }) - } -} - let hasWorkspaceFolderCapability = false connection.onInitialize((params: InitializeParams) => { @@ -122,29 +94,41 @@ connection.onInitialize((params: InitializeParams) => { }) connection.onInitialized(() => { - connection.client.register(DidChangeConfigurationNotification.type, null) + try { + connection.client.register(DidChangeConfigurationNotification.type, null) - if (hasWorkspaceFolderCapability) { - connection.workspace.onDidChangeWorkspaceFolders((_event) => { - connection.console.log('Workspace folder change event received.') - }) + if (hasWorkspaceFolderCapability) { + connection.workspace.onDidChangeWorkspaceFolders((_event) => { + connection.console.log('Workspace folder change event received.') + }) + } + initializeSettings(connection) + environmentProvider.resetEnvironment() + } catch (error) { + handleError('onInitialized failed', error) } - initializeSettings(connection) - environmentProvider.resetEnvironment() }) // Cache the settings of all open documents const documentSettings: Map> = new Map() connection.onDidChangeConfiguration(() => { - connection.workspace.getConfiguration('wollokLSP').then(settings => { - config.next(settings as ClientConfigurations) - }) + try { + connection.workspace.getConfiguration('wollokLSP').then(settings => { + config.next(settings as ClientConfigurations) + }) + } catch (error) { + handleError('onDidChangeConfiguration failed', error) + } }) // Only keep settings for open documents documents.onDidClose((change) => { - documentSettings.delete(change.document.uri) + try { + documentSettings.delete(change.document.uri) + } catch (error) { + handleError('onDidClose event failed', error) + } }) const deferredChanges: TextDocumentChangeEvent[] = [] @@ -161,8 +145,7 @@ const rebuildTextDocument = (change: TextDocumentChangeEvent) => { environmentProvider.$environment.getValue()! ) } catch (e) { - connection.console.error(`✘ Failed to rebuild document: ${e}`) - logger.error(`✘ Failed to rebuild document`, e) + handleError('Failed to rebuild document', e) } } @@ -172,24 +155,45 @@ documents.onDidChangeContent(rebuildTextDocument) // Custom requests from client connection.onRequest((change) => { - if (change.startsWith('WORKSPACE_URI')) { // WORKSPACE_URI:[uri] - setWorkspaceUri('file:' + change.split(':').pop()) - deferredChanges.forEach(rebuildTextDocument) - deferredChanges.length = 0 - } + logger.info(`onRequest - ${change}`) + try { + if (change.startsWith('WORKSPACE_URI')) { // WORKSPACE_URI:[uri] + setWorkspaceUri('file:' + change.split(':').pop()) + deferredChanges.forEach(rebuildTextDocument) + deferredChanges.length = 0 + } - if (change === 'STRONG_FILES_CHANGED') { // A file was deleted, renamed, moved, etc. - environmentProvider.resetEnvironment() - environmentProvider.updateEnvironmentWith(...documents.all()) + if (change.startsWith('STRONG_FILES_CHANGED')) { // A file was deleted, renamed, moved, etc. + environmentProvider.resetEnvironment() + environmentProvider.updateEnvironmentWith(...documents.all()) + + // Remove zombies problems + const files = change.split(':').pop() + if (files) { + const uris = files.split(',') + setTimeout(() => { + uris.forEach(uri => { + logger.info(`Removing diagnostics from ${uri}`) + connection.sendDiagnostics({ uri, diagnostics: [] }) + }) + }, 100) + } + } + } catch (error) { + handleError('onRequest change failed', error) } }) config.subscribe(() => { - // Revalidate all open text documents - environmentProvider.updateEnvironmentWith(...documents.all()) - documents.all().forEach(doc => - validateTextDocument(connection, documents.all())(doc)(environmentProvider.$environment.getValue()!) - ) + try { + // Revalidate all open text documents + environmentProvider.updateEnvironmentWith(...documents.all()) + documents.all().forEach(doc => + validateTextDocument(connection, documents.all())(doc)(environmentProvider.$environment.getValue()!) + ) + } catch (error) { + handleError('Updating environment failed', error) + } }) const handlers: readonly [ @@ -209,13 +213,21 @@ const handlers: readonly [ [connection.onReferences, references], ] -for (const [handlerRegistration, requestHandler] of handlers) { - handlerRegistration(waitForFirstHandler(requestHandler)) +try { + for (const [handlerRegistration, requestHandler] of handlers) { + handlerRegistration(waitForFirstHandler(requestHandler)) + } +} catch (error) { + handleError('Handling registration for first time failed', error) } requestContext.subscribe(([newEnvironment, newSettings]) => { - for (const [handlerRegistration, requestHandler] of handlers) { - handlerRegistration(syncHandler(requestHandler(newEnvironment!, newSettings))) + try { + for (const [handlerRegistration, requestHandler] of handlers) { + handlerRegistration(syncHandler(requestHandler(newEnvironment!, newSettings))) + } + } catch (error) { + handleError('There was an error while processing a request during a change of environment', error) } }) @@ -224,4 +236,41 @@ requestContext.subscribe(([newEnvironment, newSettings]) => { documents.listen(connection) // Listen on the connection -connection.listen() \ No newline at end of file +connection.listen() + +/*************************************************************************************************************/ +/* Internal functions */ +/*************************************************************************************************************/ + +function handleError(message: string, e: unknown): void { + connection.console.error(`✘ ${message}: ${e}`) + logger.error(`✘ ${message}`, e) +} + +function syncHandler(requestHandler: ServerRequestHandler): ServerRequestHandler { + return (params, cancel, workDoneProgress, resultProgress) => { + requestProgressReporter.begin() + try { + return requestHandler(params, cancel, workDoneProgress, resultProgress) + } finally { + requestProgressReporter.end() + } + } +} + +function waitForFirstHandler(requestHandler: (environment: Environment, settings: ClientConfigurations) => ServerRequestHandler): ServerRequestHandler { + return (params, cancel, workDoneProgress, resultProgress) => { + requestProgressReporter.begin() + return new Promise(resolve => { + firstValueFrom(requestContext).then(([newEnvironment, newSettings]) => { + const result = syncHandler(requestHandler(newEnvironment!, newSettings))(params, cancel, workDoneProgress, resultProgress) + requestProgressReporter.end() + resolve(result) + }, + error => { + requestProgressReporter.end() + throw error + }) + }) + } +} diff --git a/server/src/settings.ts b/server/src/settings.ts index dac5f7e6..4fa8ccba 100644 --- a/server/src/settings.ts +++ b/server/src/settings.ts @@ -1,4 +1,5 @@ import { Connection } from 'vscode-languageserver/node' +import { wollokLSPExtensionCode } from './shared-definitions' export interface WollokLSPSettings { maxNumberOfProblems: number @@ -7,6 +8,7 @@ export interface WollokLSPSettings { openInternalDynamicDiagram: boolean, dynamicDiagramDarkMode: boolean, maxThreshold: number, + millisecondsToOpenDynamicDiagram: number, } // ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════ @@ -29,6 +31,7 @@ const defaultSettings: WollokLSPSettings = { openInternalDynamicDiagram: true, dynamicDiagramDarkMode: true, maxThreshold: 100, + millisecondsToOpenDynamicDiagram: 1000, } let globalSettings: WollokLSPSettings = defaultSettings @@ -46,7 +49,7 @@ export const updateDocumentSettings = async ( ): Promise => { globalSettings = ((await connection.workspace.getConfiguration({ - section: 'wollokLSP', + section: wollokLSPExtensionCode, })) as WollokLSPSettings) || defaultSettings } @@ -59,4 +62,4 @@ export const initializeSettings = async ( export const lang = (): string => languageDescription[globalSettings.language] || envLang() -export const maxThreshold = (): number => globalSettings.maxThreshold \ No newline at end of file +export const maxThreshold = (): number => globalSettings.maxThreshold diff --git a/server/src/utils/vm/environment.ts b/server/src/utils/vm/environment.ts index 6de5786a..89ce629d 100644 --- a/server/src/utils/vm/environment.ts +++ b/server/src/utils/vm/environment.ts @@ -4,7 +4,6 @@ import { TextDocument } from 'vscode-languageserver-textdocument' import { Environment, buildEnvironment, inferTypes } from 'wollok-ts' import { ProgressReporter } from '../progress-reporter' import { TimeMeasurer } from '../../time-measurer' -import { logger } from '../logger' import { generateErrorForFile } from '../../linter' import { documentToFile } from '../text-documents' @@ -38,17 +37,9 @@ export class EnvironmentProvider { } return environment } catch (error) { - - // todo: remove this catch and move the logs to server.ts - const message = `✘ Failed to build environment: ${error}` documents.forEach(document => { generateErrorForFile(this.connection, document) }) - logger.error({ - level: 'error', - files: files.map(file => file.name), - message, - }) throw error } finally { this.buildProgressReporter.end() diff --git a/server/src/utils/vm/wollok.ts b/server/src/utils/vm/wollok.ts deleted file mode 100644 index 1d99224e..00000000 --- a/server/src/utils/vm/wollok.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Environment, Package } from 'wollok-ts' - -export const projectPackages = (environment: Environment): Package[] => - environment.members.slice(1) - -export const isImportedIn = (importedPackage: Package, importingPackage: Package): boolean => - importedPackage !== importingPackage && - !importingPackage.imports.some(imported => imported.entity.target === importedPackage) && - !importedPackage.isGlobalPackage \ No newline at end of file