From 5f9e32139027a2c4696a22d78a18783102b47d85 Mon Sep 17 00:00:00 2001 From: Fernando Dodino Date: Mon, 2 Oct 2023 18:09:26 -0300 Subject: [PATCH] Epic - Dynamic diagram on LSP IDE (#99) * repl: avoid erroring if no Wollok file is selected * Add configuration for REPL / dynamic diagram * Fix #101 - warn if wollok-cli path is not set * Fix #98 - kill all terminals when running REPL * Fix #101 - Fix tests * Add dark mode configuration * Fix tests for dark mode --- client/src/commands.ts | 43 +++++++++++++++------- client/src/test/commands.test.ts | 15 +++++++- package.json | 51 +++++++++++++++++++------- server/src/functionalities/reporter.ts | 40 ++++++++++++++------ server/src/linter.ts | 4 +- server/src/server.ts | 19 +--------- server/src/settings.ts | 18 ++++++--- 7 files changed, 124 insertions(+), 66 deletions(-) diff --git a/client/src/commands.ts b/client/src/commands.ts index 77b62791..ae04a091 100644 --- a/client/src/commands.ts +++ b/client/src/commands.ts @@ -50,20 +50,34 @@ export const runAllTests = (): Task => '--skipValidations', ]) -export const startRepl = (): Task => { - const currentDocument = window.activeTextEditor.document - const currentFileName = path.basename(currentDocument.uri.path) +const getCurrentFileName = (document: vscode.TextDocument | undefined) => + document ? path.basename(document.uri.path) : 'Synthetic File' - const replTask = wollokCLITask('repl', `Wollok Repl: ${currentFileName}`, [ - 'repl', - fsToShell(currentDocument.uri.fsPath), - '--skipValidations', - ]) +const getFiles = (document: vscode.TextDocument | undefined) => + document ? [fsToShell(document.uri.fsPath)] : [] - setTimeout(() => { - vscode.commands.executeCommand('simpleBrowser.show', 'http://localhost:3000/') - }, 1000) +const DYNAMIC_DIAGRAM_URI = 'http://localhost:3000/' +export const startRepl = (): Task => { + const currentDocument = window.activeTextEditor?.document + const wollokLSPConfiguration = workspace.getConfiguration('wollokLSP') + const dynamicDiagramDarkMode = wollokLSPConfiguration.get('dynamicDiagramDarkMode') ?? false + const cliCommands = [`repl`, ...getFiles(currentDocument), '--skipValidations', dynamicDiagramDarkMode ? '--darkMode' : ''] + // 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 + if (openInternalDynamicDiagram) { + vscode.commands.executeCommand('simpleBrowser.show', DYNAMIC_DIAGRAM_URI) + } else { + vscode.env.openExternal(vscode.Uri.parse(DYNAMIC_DIAGRAM_URI)) + } + }, 1000) + } return replTask } @@ -80,9 +94,10 @@ const registerCLICommand = ( ) const wollokCLITask = (task: string, name: string, cliCommands: string[]) => { - const wollokCli = unknownToShell( - workspace.getConfiguration('wollokLinter').get('cli-path'), - ) + const wollokCliPath: string = workspace.getConfiguration('wollokLSP').get('cli-path') + // TODO: i18n - but it's in the server + if (!wollokCliPath) throw new Error('Missing configuration WollokLSP/cli-path in order to run Wollok tasks') + const wollokCli = unknownToShell(wollokCliPath) const folder = workspace.workspaceFolders[0] const shellCommand = [ wollokCli, diff --git a/client/src/test/commands.test.ts b/client/src/test/commands.test.ts index 7cf44fe4..e97a3b0a 100644 --- a/client/src/test/commands.test.ts +++ b/client/src/test/commands.test.ts @@ -1,14 +1,25 @@ import * as assert from 'assert' import * as sinon from 'sinon' -import { ShellExecution, Task, Uri, env } from 'vscode' +import { ShellExecution, Task, Uri, env, workspace } from 'vscode' import { runAllTests, runProgram, runTests, startRepl } from '../commands' import { activate, getDocumentURI, getFolderURI } from './helper' import { toPosix, toWin, Shell } from '../platform-string-utils' +import { afterEach, beforeEach } from 'mocha' suite('Should run commands', () => { const folderURI = getFolderURI() const pepitaURI = getDocumentURI('pepita.wlk') + beforeEach(() => { + sinon.stub(workspace, 'getConfiguration').value((_configuration: string) => ({ + get: (_value: string) => '/usr/bin/wollok-ts-cli', + })) + }) + + afterEach(() => { + sinon.restore() + }) + test('run program', async () => { await onWindowsBash(() => testCommand( @@ -71,7 +82,7 @@ suite('Should run commands', () => { startRepl, ` repl ${toPosix( pepitaURI.fsPath, - )} --skipValidations -p ${expectedPathByShell( + )} --skipValidations --darkMode -p ${expectedPathByShell( 'bash', folderURI.fsPath, )}`, diff --git a/package.json b/package.json index 4800d49a..127d3bfd 100644 --- a/package.json +++ b/package.json @@ -56,18 +56,13 @@ "type": "object", "title": "Wollok LSP IDE", "properties": { - "wollokLinter.cli-path": { + "wollokLSP.cli-path": { "scope": "resource", "type": "string", - "description": "Path to Wollok-CLI." + "description": "Path to Wollok-CLI.", + "order": 0 }, - "wollokLinter.maxNumberOfProblems": { - "scope": "resource", - "type": "number", - "default": 100, - "description": "Controls the maximum number of problems produced by the server." - }, - "wollokLinter.language": { + "wollokLSP.language": { "scope": "resource", "type": "string", "enum": [ @@ -76,9 +71,17 @@ "Based on Local Environment" ], "default": "Based on Local Environment", - "description": "Language used while reporting linter errors and warnings." + "description": "Language used while reporting linter errors and warnings.", + "order": 1 + }, + "wollokLSP.maxNumberOfProblems": { + "scope": "resource", + "type": "number", + "default": 100, + "description": "Controls the maximum number of problems produced by the server.", + "order": 2 }, - "wollokLinter.trace.server": { + "wollokLSP.trace.server": { "scope": "window", "type": "string", "enum": [ @@ -87,7 +90,29 @@ "verbose" ], "default": "off", - "description": "Traces the communication between VS Code and the language server." + "description": "Traces the communication between VS Code and the language server.", + "order": 3 + }, + "wollokLSP.openDynamicDiagramOnRepl": { + "scope": "resource", + "type": "boolean", + "default": true, + "description": "Opens the dynamic diagram when running the REPL.", + "order": 4 + }, + "wollokLSP.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 + }, + "wollokLSP.dynamicDiagramDarkMode": { + "scope": "resource", + "type": "boolean", + "default": true, + "description": "If true, opens dynamic diagram in Dark Mode. Otherwise, it uses Light Mode.", + "order": 6 } } }, @@ -95,7 +120,7 @@ { "command": "wollok.start.repl", "title": "Start a new REPL session", - "category": "Wollok" + "category": "Wollok" }, { "command": "wollok.run.allTests", diff --git a/server/src/functionalities/reporter.ts b/server/src/functionalities/reporter.ts index aae5d366..84a6ea98 100644 --- a/server/src/functionalities/reporter.ts +++ b/server/src/functionalities/reporter.ts @@ -5,11 +5,11 @@ import { lang } from '../settings' // VALIDATION MESSAGES DEFINITION // ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════ -type ValidationMessage = { [key: string]: string } +type Message = { [key: string]: string } const FAILURE = 'failure' -const validationMessagesEn: ValidationMessage = { +const validationMessagesEn: Message = { nameShouldBeginWithLowercase: 'The name {0} must start with lowercase', nameShouldBeginWithUppercase: 'The name {0} must start with uppercase', nameShouldNotBeKeyword: @@ -95,7 +95,7 @@ const validationMessagesEn: ValidationMessage = { [FAILURE]: 'Rule failure: ', } -const validationMessagesEs: ValidationMessage = { +const validationMessagesEs: Message = { nameShouldBeginWithLowercase: 'El nombre {0} debe comenzar con min\u00FAsculas', nameShouldBeginWithUppercase: @@ -195,9 +195,25 @@ const validationMessagesEs: ValidationMessage = { [FAILURE]: 'La siguiente regla fall\u00F3: ', } -const validationMessages: { [key: string]: ValidationMessage } = { - en: validationMessagesEn, - es: validationMessagesEs, +const MISSING_WOLLOK_TS_CLI = 'missing_wollok_ts_cli' + +const lspMessagesEn = { + [MISSING_WOLLOK_TS_CLI]: 'Missing configuration WollokLSP/cli-pat in order to run Wollok tasks', +} + +const lspMessagesEs = { + [MISSING_WOLLOK_TS_CLI]: 'Falta la configuración WollokLSP/cli-path para poder ejecutar tareas de Wollok', +} + +const messages: { [key: string]: Message } = { + en: { + ...validationMessagesEn, + ...lspMessagesEn, + }, + es: { + ...validationMessagesEs, + ...lspMessagesEs, + }, } // ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════ @@ -225,14 +241,14 @@ const interpolateValidationMessage = (message: string, ...values: string[]) => return values[index] || '' }) -const getBasicMessage = (problem: Problem) => - validationI18nized()[problem.code] || convertToHumanReadable(problem.code) - -const validationI18nized = () => validationMessages[lang()] as ValidationMessage +const validationI18nized = () => messages[lang()] as Message // ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════ // PUBLIC INTERFACE // ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════ -export const reportMessage = (problem: Problem): string => - interpolateValidationMessage(getBasicMessage(problem), ...problem.values) +export const reportValidationMessage = (problem: Problem): string => + getMessage(problem.code, problem.values.concat()) + +export const getMessage = (message: string, values: string[]): string => + interpolateValidationMessage(validationI18nized()[message] || convertToHumanReadable(message), ...values) diff --git a/server/src/linter.ts b/server/src/linter.ts index 327fcf60..cf719c02 100644 --- a/server/src/linter.ts +++ b/server/src/linter.ts @@ -25,7 +25,7 @@ import { getTestCodeLenses, } from './functionalities/code-lens' import { getNodeDefinition } from './functionalities/definition' -import { reportMessage } from './functionalities/reporter' +import { reportValidationMessage } from './functionalities/reporter' import { updateDocumentSettings } from './settings' import { documentSymbolsFor, @@ -60,7 +60,7 @@ const createDiagnostic = (textDocument: TextDocument, problem: Problem) => { severity: buildSeverity(problem), range: trimIn(range, textDocument), code: problem.code, - message: reportMessage(problem), + message: reportValidationMessage(problem), source: '', } as Diagnostic } diff --git a/server/src/server.ts b/server/src/server.ts index 4cc0e72c..79554d20 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -17,7 +17,7 @@ import { validateTextDocument, workspaceSymbols, } from './linter' -import { initializeSettings, WollokLinterSettings } from './settings' +import { initializeSettings, WollokLSPSettings } from './settings' import { templates } from './functionalities/autocomplete/templates' import { EnvironmentProvider } from './utils/vm/environment-provider' @@ -77,28 +77,13 @@ connection.onInitialized(() => { }) // Cache the settings of all open documents -const documentSettings: Map> = new Map() +const documentSettings: Map> = new Map() connection.onDidChangeConfiguration(() => { // Revalidate all open text documents documents.all().forEach(validateTextDocument(connection, documents.all())) }) -// function getDocumentSettings(resource: string): Thenable { -// if (!hasConfigurationCapability) { -// return Promise.resolve(globalSettings) -// } -// let result = documentSettings.get(resource) -// if (!result) { -// result = connection.workspace.getConfiguration({ -// scopeUri: resource, -// section: 'languageServerExample', -// }) -// documentSettings.set(resource, result) -// } -// return result -// } - // Only keep settings for open documents documents.onDidClose((e) => { documentSettings.delete(e.document.uri) diff --git a/server/src/settings.ts b/server/src/settings.ts index b9a320c3..7645cdf7 100644 --- a/server/src/settings.ts +++ b/server/src/settings.ts @@ -1,8 +1,11 @@ import { Connection } from 'vscode-languageserver/node' -export interface WollokLinterSettings { +export interface WollokLSPSettings { maxNumberOfProblems: number - language: string + language: string, + openDynamicDiagramOnRepl: boolean, + openInternalDynamicDiagram: boolean, + dynamicDiagramDarkMode: boolean, } // ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════ @@ -18,12 +21,15 @@ const envLang = () => { return fullLanguage ? fullLanguage.substring(0, 2) : SPANISH } -const defaultSettings: WollokLinterSettings = { +const defaultSettings: WollokLSPSettings = { maxNumberOfProblems: 1000, language: envLang(), + openDynamicDiagramOnRepl: true, + openInternalDynamicDiagram: true, + dynamicDiagramDarkMode: true, } -let globalSettings: WollokLinterSettings = defaultSettings +let globalSettings: WollokLSPSettings = defaultSettings const languageDescription: { [key: string]: string } = { Spanish: SPANISH, @@ -38,8 +44,8 @@ export const updateDocumentSettings = async ( ): Promise => { globalSettings = ((await connection.workspace.getConfiguration({ - section: 'wollokLinter', - })) as WollokLinterSettings) || defaultSettings + section: 'wollokLSP', + })) as WollokLSPSettings) || defaultSettings } export const initializeSettings = async (