diff --git a/README.md b/README.md index 0930b61..4a23d7e 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,16 @@ MATLAB language server supports these editors by installing the corresponding ex ### Unreleased +### 1.2.4 +Release date: 2024-07-12 + +Added: +* Improvements to code folding (requires MATLAB R2024b or later) + +Fixed: +* Allow connection to MATLAB when a single quote appears in the extension installation path +* Resolve error with code navigation when using with MATLAB R2024b + ### 1.2.3 Release date: 2024-06-14 diff --git a/matlab/+matlabls/+handlers/NavigationSupportHandler.m b/matlab/+matlabls/+handlers/NavigationSupportHandler.m index a028a32..36561bf 100644 --- a/matlab/+matlabls/+handlers/NavigationSupportHandler.m +++ b/matlab/+matlabls/+handlers/NavigationSupportHandler.m @@ -53,15 +53,19 @@ function handleResolvePathRequest (this, msg) function path = resolvePath (name, contextFile) if isMATLABReleaseOlderThan('R2023b') - % For usage in R2023b and earlier + % For usage in R2023a and earlier [isFound, path] = matlabls.internal.resolvePath(name, contextFile); elseif isMATLABReleaseOlderThan('R2024a') % For usage in R2023b only [isFound, path] = matlab.internal.language.introspective.resolveFile(name, []); - else - % For usage in R2024a and later + elseif isMATLABReleaseOlderThan('R2024b') + % For usage in R2024a only ec = matlab.lang.internal.introspective.ExecutionContext; [isFound, path] = matlab.lang.internal.introspective.resolveFile(name, ec); + else + % For usage in R2024b and later + ic = matlab.lang.internal.introspective.IntrospectiveContext.caller; + [isFound, path] = matlab.lang.internal.introspective.resolveFile(name, ic); end if ~isFound diff --git a/package-lock.json b/package-lock.json index 76f3c81..bfbaba4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matlab-language-server", - "version": "1.2.3", + "version": "1.2.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "matlab-language-server", - "version": "1.2.3", + "version": "1.2.4", "license": "MIT", "dependencies": { "chokidar": "^3.5.3", diff --git a/package.json b/package.json index 91977f8..56f8f13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matlab-language-server", - "version": "1.2.3", + "version": "1.2.4", "description": "Language Server for MATLAB code", "main": "./src/index.ts", "bin": "./out/index.js", diff --git a/src/ClientConnection.ts b/src/ClientConnection.ts new file mode 100644 index 0000000..9f8c29e --- /dev/null +++ b/src/ClientConnection.ts @@ -0,0 +1,32 @@ +// Copyright 2024 The MathWorks, Inc. +import { _Connection, createConnection, ProposedFeatures } from "vscode-languageserver/node" + +export type Connection = _Connection + +export default class ClientConnection { + private static connection: Connection + + /** + * Retrieves the connection to the client. If no connection currently exists, + * a new connection is created. + * + * @returns The connection to the client + */ + public static getConnection (): Connection { + if (ClientConnection.connection == null) { + ClientConnection.connection = createConnection(ProposedFeatures.all) + } + + return ClientConnection.connection + } + + /** + * Sets the ClientConnection to a given object. + * This API is primarily meant for testing purposes. + * + * @param connection The connection object to set + */ + public static setConnection (connection: Connection): void { + ClientConnection.connection = connection + } +} diff --git a/src/index.ts b/src/index.ts index 6d91f25..29e8aa7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,11 @@ -// Copyright 2022 - 2023 The MathWorks, Inc. +// Copyright 2022 - 2024 The MathWorks, Inc. // Start up the LSP server -import { connection } from './server' +import ClientConnection from './ClientConnection' +import * as server from './server' -// Listen on the connection -connection.listen() +// Start up the language server +server.startServer() + +// Listen on the client connection +ClientConnection.getConnection().listen() diff --git a/src/indexing/DocumentIndexer.ts b/src/indexing/DocumentIndexer.ts index 0133dbe..1b55045 100644 --- a/src/indexing/DocumentIndexer.ts +++ b/src/indexing/DocumentIndexer.ts @@ -10,9 +10,11 @@ const INDEXING_DELAY = 500 // Delay (in ms) after keystroke before attempting to * Handles indexing a currently open document to gather data about classes, * functions, and variables. */ -class DocumentIndexer { +export default class DocumentIndexer { private readonly pendingFilesToIndex = new Map() + constructor (private indexer: Indexer) {} + /** * Queues a document to be indexed. This handles debouncing so that * indexing is not performed on every keystroke. @@ -36,7 +38,7 @@ class DocumentIndexer { * @param textDocument The document being indexed */ indexDocument (textDocument: TextDocument): void { - void Indexer.indexDocument(textDocument) + void this.indexer.indexDocument(textDocument) } /** @@ -63,12 +65,10 @@ class DocumentIndexer { const uri = textDocument.uri if (this.pendingFilesToIndex.has(uri)) { this.clearTimerForDocumentUri(uri) - await Indexer.indexDocument(textDocument) + await this.indexer.indexDocument(textDocument) } if (!FileInfoIndex.codeDataCache.has(uri)) { - await Indexer.indexDocument(textDocument) + await this.indexer.indexDocument(textDocument) } } } - -export default new DocumentIndexer() diff --git a/src/indexing/FileInfoIndex.ts b/src/indexing/FileInfoIndex.ts index cad83c7..156f2a7 100644 --- a/src/indexing/FileInfoIndex.ts +++ b/src/indexing/FileInfoIndex.ts @@ -95,6 +95,8 @@ export enum FunctionVisibility { * Serves as an cache of data extracted from files */ class FileInfoIndex { + private static instance: FileInfoIndex + /** * Maps document URI to the code data */ @@ -105,6 +107,14 @@ class FileInfoIndex { */ readonly classInfoCache = new Map() + public static getInstance (): FileInfoIndex { + if (FileInfoIndex.instance == null) { + FileInfoIndex.instance = new FileInfoIndex() + } + + return FileInfoIndex.instance + } + /** * Parses the raw data into a more usable form. Caches the resulting data * in the code data index. @@ -489,4 +499,4 @@ function convertRange (codeDataRange: CodeDataRange): Range { ) } -export default new FileInfoIndex() +export default FileInfoIndex.getInstance() diff --git a/src/indexing/Indexer.ts b/src/indexing/Indexer.ts index a8e7638..a5c22ae 100644 --- a/src/indexing/Indexer.ts +++ b/src/indexing/Indexer.ts @@ -14,20 +14,22 @@ interface WorkspaceFileIndexedResponse { codeData: RawCodeData } -class Indexer { +export default class Indexer { private readonly INDEX_DOCUMENT_REQUEST_CHANNEL = '/matlabls/indexDocument/request' private readonly INDEX_DOCUMENT_RESPONSE_CHANNEL = '/matlabls/indexDocument/response' private readonly INDEX_FOLDERS_REQUEST_CHANNEL = '/matlabls/indexFolders/request' private readonly INDEX_FOLDERS_RESPONSE_CHANNEL = '/matlabls/indexFolders/response' + constructor (private matlabLifecycleManager: MatlabLifecycleManager, private pathResolver: PathResolver) {} + /** * Indexes the given TextDocument and caches the data. * * @param textDocument The document being indexed */ async indexDocument (textDocument: TextDocument): Promise { - const matlabConnection = await MatlabLifecycleManager.getMatlabConnection() + const matlabConnection = await this.matlabLifecycleManager.getMatlabConnection() if (matlabConnection == null) { return @@ -46,7 +48,7 @@ class Indexer { * @param folders A list of folder URIs to be indexed */ async indexFolders (folders: string[]): Promise { - const matlabConnection = await MatlabLifecycleManager.getMatlabConnection() + const matlabConnection = await this.matlabLifecycleManager.getMatlabConnection() if (matlabConnection == null) { return @@ -79,7 +81,7 @@ class Indexer { * @param uri The URI for the file being indexed */ async indexFile (uri: string): Promise { - const matlabConnection = await MatlabLifecycleManager.getMatlabConnection() + const matlabConnection = await this.matlabLifecycleManager.getMatlabConnection() if (matlabConnection == null) { return @@ -143,7 +145,7 @@ class Indexer { // Find and queue indexing for parent classes const baseClasses = parsedCodeData.classInfo.baseClasses - const resolvedBaseClasses = await PathResolver.resolvePaths(baseClasses, uri, matlabConnection) + const resolvedBaseClasses = await this.pathResolver.resolvePaths(baseClasses, uri, matlabConnection) resolvedBaseClasses.forEach(resolvedBaseClass => { const uri = resolvedBaseClass.uri @@ -153,5 +155,3 @@ class Indexer { }) } } - -export default new Indexer() diff --git a/src/indexing/WorkspaceIndexer.ts b/src/indexing/WorkspaceIndexer.ts index 7cdb4b8..cf91149 100644 --- a/src/indexing/WorkspaceIndexer.ts +++ b/src/indexing/WorkspaceIndexer.ts @@ -1,17 +1,19 @@ -// Copyright 2022 - 2023 The MathWorks, Inc. +// Copyright 2022 - 2024 The MathWorks, Inc. import { ClientCapabilities, WorkspaceFolder, WorkspaceFoldersChangeEvent } from 'vscode-languageserver' import ConfigurationManager from '../lifecycle/ConfigurationManager' -import { connection } from '../server' import Indexer from './Indexer' +import ClientConnection from '../ClientConnection' /** * Handles indexing files in the user's workspace to gather data about classes, * functions, and variables. */ -class WorkspaceIndexer { +export default class WorkspaceIndexer { private isWorkspaceIndexingSupported = false + constructor (private indexer: Indexer) {} + /** * Sets up workspace change listeners, if supported. * @@ -26,7 +28,7 @@ class WorkspaceIndexer { return } - connection.workspace.onDidChangeWorkspaceFolders((params: WorkspaceFoldersChangeEvent) => { + ClientConnection.getConnection().workspace.onDidChangeWorkspaceFolders((params: WorkspaceFoldersChangeEvent) => { void this.handleWorkspaceFoldersAdded(params.added) }) } @@ -39,13 +41,13 @@ class WorkspaceIndexer { return } - const folders = await connection.workspace.getWorkspaceFolders() + const folders = await ClientConnection.getConnection().workspace.getWorkspaceFolders() if (folders == null) { return } - void Indexer.indexFolders(folders.map(folder => folder.uri)) + void this.indexer.indexFolders(folders.map(folder => folder.uri)) } /** @@ -58,7 +60,7 @@ class WorkspaceIndexer { return } - void Indexer.indexFolders(folders.map(folder => folder.uri)) + void this.indexer.indexFolders(folders.map(folder => folder.uri)) } /** @@ -73,5 +75,3 @@ class WorkspaceIndexer { return this.isWorkspaceIndexingSupported && shouldIndexWorkspace } } - -export default new WorkspaceIndexer() diff --git a/src/lifecycle/ConfigurationManager.ts b/src/lifecycle/ConfigurationManager.ts index 3766e92..c3701c5 100644 --- a/src/lifecycle/ConfigurationManager.ts +++ b/src/lifecycle/ConfigurationManager.ts @@ -1,9 +1,9 @@ -// Copyright 2022 - 2023 The MathWorks, Inc. +// Copyright 2022 - 2024 The MathWorks, Inc. import { ClientCapabilities, DidChangeConfigurationNotification, DidChangeConfigurationParams } from 'vscode-languageserver' import { reportTelemetrySettingsChange } from '../logging/TelemetryUtils' -import { connection } from '../server' import { getCliArgs } from '../utils/CliUtils' +import ClientConnection from '../ClientConnection' export enum Argument { // Basic arguments @@ -45,6 +45,8 @@ const SETTING_NAMES: SettingName[] = [ ] class ConfigurationManager { + private static instance: ConfigurationManager + private configuration: Settings | null = null private readonly defaultConfiguration: Settings private globalSettings: Settings @@ -77,12 +79,22 @@ class ConfigurationManager { } } + public static getInstance (): ConfigurationManager { + if (ConfigurationManager.instance == null) { + ConfigurationManager.instance = new ConfigurationManager + } + + return ConfigurationManager.instance + } + /** * Sets up the configuration manager * * @param capabilities The client capabilities */ setup (capabilities: ClientCapabilities): void { + const connection = ClientConnection.getConnection() + this.hasConfigurationCapability = capabilities.workspace?.configuration != null if (this.hasConfigurationCapability) { @@ -101,6 +113,7 @@ class ConfigurationManager { async getConfiguration (): Promise { if (this.hasConfigurationCapability) { if (this.configuration == null) { + const connection = ClientConnection.getConnection() this.configuration = await connection.workspace.getConfiguration('MATLAB') as Settings } @@ -164,4 +177,4 @@ class ConfigurationManager { } } -export default new ConfigurationManager() +export default ConfigurationManager.getInstance() diff --git a/src/lifecycle/LifecycleNotificationHelper.ts b/src/lifecycle/LifecycleNotificationHelper.ts index 34badb2..3ddd996 100644 --- a/src/lifecycle/LifecycleNotificationHelper.ts +++ b/src/lifecycle/LifecycleNotificationHelper.ts @@ -4,8 +4,18 @@ import NotificationService, { Notification } from '../notifications/Notification import { ConnectionState } from './MatlabSession' class LifecycleNotificationHelper { + private static instance: LifecycleNotificationHelper + didMatlabLaunchFail = false + public static getInstance (): LifecycleNotificationHelper { + if (LifecycleNotificationHelper.instance == null) { + LifecycleNotificationHelper.instance = new LifecycleNotificationHelper() + } + + return LifecycleNotificationHelper.instance + } + /** * Sends notification to the language client of a change in the MATLAB® connection state. * @@ -27,4 +37,4 @@ class LifecycleNotificationHelper { } } -export default new LifecycleNotificationHelper() +export default LifecycleNotificationHelper.getInstance() diff --git a/src/lifecycle/MatlabLifecycleManager.ts b/src/lifecycle/MatlabLifecycleManager.ts index 9adc135..b29d10f 100644 --- a/src/lifecycle/MatlabLifecycleManager.ts +++ b/src/lifecycle/MatlabLifecycleManager.ts @@ -6,7 +6,7 @@ import ConfigurationManager, { Argument, ConnectionTiming } from "./Configuratio import { MatlabConnection } from "./MatlabCommunicationManager" import MatlabSession, { launchNewMatlab, connectToMatlab } from './MatlabSession' -class MatlabLifecycleManager { +export default class MatlabLifecycleManager { eventEmitter = new EventEmitter() private matlabSession: MatlabSession | null = null @@ -182,5 +182,3 @@ function shouldConnectToRemoteMatlab (): boolean { // Assume we should connect to existing MATLAB if the matlabUrl startup flag has been provided return Boolean(ConfigurationManager.getArgument(Argument.MatlabUrl)) } - -export default new MatlabLifecycleManager() diff --git a/src/lifecycle/MatlabSession.ts b/src/lifecycle/MatlabSession.ts index f96f43a..0ad9c8b 100644 --- a/src/lifecycle/MatlabSession.ts +++ b/src/lifecycle/MatlabSession.ts @@ -398,7 +398,7 @@ async function getMatlabLaunchCommand (outFile: string): Promise<{ command: stri '-memmgr', 'release', // Memory manager '-noAppIcon', // Hide MATLAB application icon in taskbar/dock, if applicable '-nosplash', // Hide splash screen - '-r', `addpath(fullfile('${__dirname}', '..', 'matlab')); initmatlabls('${outFile}')`, // Startup command + '-r', getMatlabStartupCommand(outFile), // Startup command '-useStartupFolderPref', // Startup folder flag '-nodesktop' // Hide the MATLAB desktop ] @@ -418,3 +418,23 @@ async function getMatlabLaunchCommand (outFile: string): Promise<{ command: stri args } } + +/** + * Gets the MATLAB command which the MATLAB application should run at startup. + * + * Note: This will sanitize the file paths so that they can be safely used within + * character vectors in MATLAB. This is done by replacing all single-quote characters + * with double single-quotes. + * + * @param outFile The file in which MATLAB should output connection details + * @returns The MATLAB startup command + */ +function getMatlabStartupCommand (outFile: string): string { + // Sanitize file paths for MATLAB: + // Replace single-quotes in the file path with double single-quotes + // to preserve the quote when used within a MATLAB character vector. + const extensionInstallationDir = __dirname.replace(/'/g, "''") + const outFilePath = outFile.replace(/'/g, "''") + + return `addpath(fullfile('${extensionInstallationDir}', '..', 'matlab')); initmatlabls('${outFilePath}')` +} diff --git a/src/logging/Logger.ts b/src/logging/Logger.ts index 740fc6b..1a675db 100644 --- a/src/logging/Logger.ts +++ b/src/logging/Logger.ts @@ -1,43 +1,50 @@ -// Copyright 2022 - 2023 The MathWorks, Inc. +// Copyright 2022 - 2024 The MathWorks, Inc. import * as fs from 'fs' import * as os from 'os' import * as path from 'path' import { RemoteConsole } from 'vscode-languageserver' +import ClientConnection from '../ClientConnection' const SERVER_LOG = 'languageServerLog.txt' const MATLAB_LOG = 'matlabLog.txt' class Logger { - private readonly _logDir: string + private static instance: Logger + + public readonly logDir: string private readonly languageServerLogFile: string private readonly matlabLogFile: string - private _console: RemoteConsole | null = null + + private readonly console: RemoteConsole constructor () { // Create Log Directory const pid = process.pid - this._logDir = path.join(os.tmpdir(), `matlabls_${pid}`) - if (fs.existsSync(this._logDir)) { + this.logDir = path.join(os.tmpdir(), `matlabls_${pid}`) + if (fs.existsSync(this.logDir)) { let i = 1 - while (fs.existsSync(`${this._logDir}_${i}`)) { i++ } - this._logDir = `${this._logDir}_${i}` + while (fs.existsSync(`${this.logDir}_${i}`)) { i++ } + this.logDir = `${this.logDir}_${i}` } - fs.mkdirSync(this._logDir) + fs.mkdirSync(this.logDir) // Get name of log file - this.languageServerLogFile = path.join(this._logDir, SERVER_LOG) - this.matlabLogFile = path.join(this._logDir, MATLAB_LOG) + this.languageServerLogFile = path.join(this.logDir, SERVER_LOG) + this.matlabLogFile = path.join(this.logDir, MATLAB_LOG) + + // Get log console + this.console = ClientConnection.getConnection().console + + this.log(`Log Directory: ${this.logDir}`) } - /** - * Initializes the logger with an output console. - * - * @param console The console which the Logger should output to - */ - initialize (console: RemoteConsole): void { - this._console = console - this.log(`Log Directory: ${this._logDir}`) + public static getInstance (): Logger { + if (Logger.instance == null) { + Logger.instance = new Logger() + } + + return Logger.instance } /** @@ -47,7 +54,7 @@ class Logger { */ log (message: string): void { const msg = `(${getCurrentTimeString()}) matlabls: ${message}` - this._console?.log(msg) + this.console?.log(msg) this._writeToLogFile(msg, this.languageServerLogFile) } @@ -58,7 +65,7 @@ class Logger { */ warn (message: string): void { const msg = `(${getCurrentTimeString()}) matlabls - WARNING: ${message}` - this._console?.warn(msg) + this.console?.warn(msg) this._writeToLogFile(msg, this.languageServerLogFile) } @@ -69,7 +76,7 @@ class Logger { */ error (message: string): void { const msg = `(${getCurrentTimeString()}) matlabls - ERROR: ${message}` - this._console?.error(msg) + this.console?.error(msg) this._writeToLogFile(msg, this.languageServerLogFile) } @@ -83,10 +90,6 @@ class Logger { this._writeToLogFile(message, this.matlabLogFile) } - public get logDir (): string { - return this._logDir - } - private _writeToLogFile (message: string, filePath: string): void { // Log to file fs.writeFile( @@ -95,7 +98,7 @@ class Logger { { flag: 'a+' }, err => { if (err !== null) { - this._console?.error('Failed to write to log file') + this.console?.error('Failed to write to log file') } } ) @@ -108,4 +111,4 @@ function getCurrentTimeString (): string { return `${strFormatter(d.getHours())}:${strFormatter(d.getMinutes())}:${strFormatter(d.getSeconds())}` } -export default new Logger() +export default Logger.getInstance() diff --git a/src/notifications/NotificationService.ts b/src/notifications/NotificationService.ts index 91eb7c7..1fd0f38 100644 --- a/src/notifications/NotificationService.ts +++ b/src/notifications/NotificationService.ts @@ -1,7 +1,7 @@ // Copyright 2022 - 2024 The MathWorks, Inc. import { GenericNotificationHandler } from 'vscode-languageserver' -import { connection } from '../server' +import ClientConnection from '../ClientConnection' export enum Notification { // Connection Status Updates @@ -36,6 +36,16 @@ export enum Notification { } class NotificationService { + private static instance: NotificationService + + public static getInstance (): NotificationService { + if (NotificationService.instance == null) { + NotificationService.instance = new NotificationService() + } + + return NotificationService.instance + } + /** * Sends a notification to the language client * @@ -43,7 +53,7 @@ class NotificationService { * @param params Any parameters to send with the notification */ sendNotification (name: string, params?: unknown): void { - void connection.sendNotification(name, params) + void ClientConnection.getConnection().sendNotification(name, params) } /** @@ -53,8 +63,8 @@ class NotificationService { * @param callback The callback */ registerNotificationListener (name: string, callback: GenericNotificationHandler): void { - connection.onNotification(name, callback) + ClientConnection.getConnection().onNotification(name, callback) } } -export default new NotificationService() +export default NotificationService.getInstance() diff --git a/src/providers/completion/CompletionSupportProvider.ts b/src/providers/completion/CompletionSupportProvider.ts index 70e3985..6cc78bd 100644 --- a/src/providers/completion/CompletionSupportProvider.ts +++ b/src/providers/completion/CompletionSupportProvider.ts @@ -78,10 +78,12 @@ const MatlabCompletionToKind: { [index: string]: CompletionItemKind } = { * Handles requests for completion-related features. * Currently, this handles auto-completion as well as function signature help. */ -class CompletionProvider { +class CompletionSupportProvider { private readonly REQUEST_CHANNEL = '/matlabls/completions/request' private readonly RESPONSE_CHANNEL = '/matlabls/completions/response' + constructor (private matlabLifecycleManager: MatlabLifecycleManager) {} + /** * Handles a request for auto-completion choices. * @@ -134,7 +136,7 @@ class CompletionProvider { const fileName = URI.parse(docUri).fsPath const cursorPosition = doc.offsetAt(position) - const matlabConnection = await MatlabLifecycleManager.getMatlabConnection() + const matlabConnection = await this.matlabLifecycleManager.getMatlabConnection() if (matlabConnection == null) { return {} @@ -333,4 +335,4 @@ class CompletionProvider { } } -export default new CompletionProvider() +export default CompletionSupportProvider diff --git a/src/providers/folding/FoldingSupportProvider.ts b/src/providers/folding/FoldingSupportProvider.ts index aaebaee..192415d 100644 --- a/src/providers/folding/FoldingSupportProvider.ts +++ b/src/providers/folding/FoldingSupportProvider.ts @@ -11,15 +11,17 @@ class FoldingSupportProvider { private readonly REQUEST_CHANNEL = '/matlabls/foldDocument/request' private readonly RESPONSE_CHANNEL = '/matlabls/foldDocument/response' + constructor (private matlabLifecycleManager: MatlabLifecycleManager) {} + async handleFoldingRangeRequest (params: FoldingRangeParams, documentManager: TextDocuments): Promise { const docToFold = documentManager.get(params.textDocument.uri) if (docToFold == null) { return null } - const matlabConnection = await MatlabLifecycleManager.getMatlabConnection() + const matlabConnection = await this.matlabLifecycleManager.getMatlabConnection() const isMatlabAvailable = (matlabConnection != null) - const matlabRelease = MatlabLifecycleManager.getMatlabRelease() + const matlabRelease = this.matlabLifecycleManager.getMatlabRelease() // check for connection and release if (!isMatlabAvailable || (matlabRelease == null) || (matlabRelease < 'R2024b')) { @@ -79,4 +81,4 @@ class FoldingSupportProvider { } } -export default new FoldingSupportProvider() +export default FoldingSupportProvider diff --git a/src/providers/formatting/FormatSupportProvider.ts b/src/providers/formatting/FormatSupportProvider.ts index 43649ad..a2e9101 100644 --- a/src/providers/formatting/FormatSupportProvider.ts +++ b/src/providers/formatting/FormatSupportProvider.ts @@ -5,7 +5,6 @@ import { TextDocument } from 'vscode-languageserver-textdocument' import LifecycleNotificationHelper from '../../lifecycle/LifecycleNotificationHelper' import MatlabLifecycleManager from '../../lifecycle/MatlabLifecycleManager' import { ActionErrorConditions, Actions, reportTelemetryAction } from '../../logging/TelemetryUtils' -import { connection } from '../../server' import * as TextDocumentUtils from '../../utils/TextDocumentUtils' interface FormatDocumentResponse { @@ -21,6 +20,8 @@ class FormatSupportProvider { private readonly REQUEST_CHANNEL = '/matlabls/formatDocument/request' private readonly RESPONSE_CHANNEL = '/matlabls/formatDocument/response' + constructor (private matlabLifecycleManager: MatlabLifecycleManager) {} + /** * Handles a request for document formatting. * @@ -47,7 +48,7 @@ class FormatSupportProvider { */ private async formatDocument (doc: TextDocument, options: FormattingOptions): Promise { // For format, we try to instantiate MATLAB® if it is not already running - const matlabConnection = await MatlabLifecycleManager.getMatlabConnection(true) + const matlabConnection = await this.matlabLifecycleManager.getMatlabConnection(true) // If MATLAB is not available, no-op if (matlabConnection == null) { @@ -81,4 +82,4 @@ class FormatSupportProvider { } } -export default new FormatSupportProvider() +export default FormatSupportProvider diff --git a/src/providers/linting/LintingSupportProvider.ts b/src/providers/linting/LintingSupportProvider.ts index ff3da3a..5b10a10 100644 --- a/src/providers/linting/LintingSupportProvider.ts +++ b/src/providers/linting/LintingSupportProvider.ts @@ -12,7 +12,7 @@ import * as fs from 'fs/promises' import * as path from 'path' import which = require('which') import { MatlabLSCommands } from '../lspCommands/ExecuteCommandProvider' -import { connection } from '../../server' +import ClientConnection from '../../ClientConnection' type mlintSeverity = '0' | '1' | '2' | '3' | '4' @@ -58,6 +58,8 @@ class LintingSupportProvider { private readonly _pendingFilesToLint = new Map() private readonly _availableCodeActions = new Map() + constructor (private matlabLifecycleManager: MatlabLifecycleManager) {} + /** * Queues a document to be linted. This handles debouncing so * that linting is not performed on every keystroke. @@ -87,7 +89,7 @@ class LintingSupportProvider { this.clearTimerForDocumentUri(uri) this.clearCodeActionsForDocumentUri(uri) - const matlabConnection = await MatlabLifecycleManager.getMatlabConnection() + const matlabConnection = await this.matlabLifecycleManager.getMatlabConnection() const isMatlabAvailable = matlabConnection != null const fileName = URI.parse(uri).fsPath @@ -110,14 +112,14 @@ class LintingSupportProvider { this._availableCodeActions.set(uri, lintResults.codeActions) // Report diagnostics - void connection.sendDiagnostics({ + void ClientConnection.getConnection().sendDiagnostics({ uri, diagnostics }) } clearDiagnosticsForDocument (textDocument: TextDocument): void { - void connection.sendDiagnostics({ + void ClientConnection.getConnection().sendDiagnostics({ uri: textDocument.uri, diagnostics: [] }) @@ -143,7 +145,7 @@ class LintingSupportProvider { return params.context.diagnostics.some(diag => this.isSameDiagnostic(diagnostic, diag)) }) - if (!MatlabLifecycleManager.isMatlabConnected()) { + if (!this.matlabLifecycleManager.isMatlabConnected()) { // Cannot suppress warnings without MATLAB return codeActions } @@ -199,7 +201,7 @@ class LintingSupportProvider { * @param shouldSuppressThroughoutFile Whether or not to suppress the diagnostic throughout the entire file */ async suppressDiagnostic (textDocument: TextDocument, range: Range, id: string, shouldSuppressThroughoutFile: boolean): Promise { - const matlabConnection = await MatlabLifecycleManager.getMatlabConnection() + const matlabConnection = await this.matlabLifecycleManager.getMatlabConnection() if (matlabConnection == null) { return } @@ -223,7 +225,7 @@ class LintingSupportProvider { ] } - void connection.workspace.applyEdit(edit) + void ClientConnection.getConnection().workspace.applyEdit(edit) }) matlabConnection.publish(this.SUPPRESS_DIAGNOSTIC_REQUEST_CHANNEL, { @@ -528,4 +530,4 @@ class LintingSupportProvider { } } -export default new LintingSupportProvider() +export default LintingSupportProvider diff --git a/src/providers/lspCommands/ExecuteCommandProvider.ts b/src/providers/lspCommands/ExecuteCommandProvider.ts index f5c41d0..b206a11 100644 --- a/src/providers/lspCommands/ExecuteCommandProvider.ts +++ b/src/providers/lspCommands/ExecuteCommandProvider.ts @@ -1,4 +1,4 @@ -// Copyright 2022 - 2023 The MathWorks, Inc. +// Copyright 2022 - 2024 The MathWorks, Inc. import { ExecuteCommandParams, Range, TextDocuments, _Connection } from 'vscode-languageserver' import { TextDocument } from 'vscode-languageserver-textdocument' @@ -19,6 +19,8 @@ export const MatlabLSCommands = { * Handles requests to execute commands */ class ExecuteCommandProvider { + constructor (private lintingSupportProvider: LintingSupportProvider) {} + /** * Handles command execution requests. * @@ -52,8 +54,8 @@ class ExecuteCommandProvider { } const shouldSuppressThroughoutFile = params.command === MatlabLSCommands.MLINT_SUPPRESS_IN_FILE - void LintingSupportProvider.suppressDiagnostic(doc, range, args.id, shouldSuppressThroughoutFile) + void this.lintingSupportProvider.suppressDiagnostic(doc, range, args.id, shouldSuppressThroughoutFile) } } -export default new ExecuteCommandProvider() +export default ExecuteCommandProvider diff --git a/src/providers/navigation/NavigationSupportProvider.ts b/src/providers/navigation/NavigationSupportProvider.ts index 85936e7..70495eb 100644 --- a/src/providers/navigation/NavigationSupportProvider.ts +++ b/src/providers/navigation/NavigationSupportProvider.ts @@ -1,4 +1,4 @@ -// Copyright 2022 - 2023 The MathWorks, Inc. +// Copyright 2022 - 2024 The MathWorks, Inc. import { DefinitionParams, DocumentSymbolParams, Location, Position, Range, ReferenceParams, SymbolInformation, SymbolKind, TextDocuments } from 'vscode-languageserver' import { TextDocument } from 'vscode-languageserver-textdocument' @@ -10,7 +10,6 @@ import { MatlabConnection } from '../../lifecycle/MatlabCommunicationManager' import MatlabLifecycleManager from '../../lifecycle/MatlabLifecycleManager' import { getTextOnLine } from '../../utils/TextDocumentUtils' import PathResolver from './PathResolver' -import { connection } from '../../server' import LifecycleNotificationHelper from '../../lifecycle/LifecycleNotificationHelper' import { ActionErrorConditions, Actions, reportTelemetryAction } from '../../logging/TelemetryUtils' import DocumentIndexer from '../../indexing/DocumentIndexer' @@ -87,6 +86,13 @@ function reportTelemetry (type: RequestType, errorCondition = ''): void { class NavigationSupportProvider { private readonly DOTTED_IDENTIFIER_REGEX = /[\w.]+/ + constructor ( + private matlabLifecycleManager: MatlabLifecycleManager, + private indexer: Indexer, + private documentIndexer: DocumentIndexer, + private pathResolver: PathResolver + ) {} + /** * Handles requests for definitions or references. * @@ -96,7 +102,7 @@ class NavigationSupportProvider { * @returns An array of locations */ async handleDefOrRefRequest (params: DefinitionParams | ReferenceParams, documentManager: TextDocuments, requestType: RequestType): Promise { - const matlabConnection = await MatlabLifecycleManager.getMatlabConnection(true) + const matlabConnection = await this.matlabLifecycleManager.getMatlabConnection(true) if (matlabConnection == null) { LifecycleNotificationHelper.notifyMatlabRequirement() reportTelemetry(requestType, ActionErrorConditions.MatlabUnavailable) @@ -148,13 +154,13 @@ class NavigationSupportProvider { // However, simply returning [] in this case could cause a delay between MATLAB started // and the symbols being identified. const matlabConnection = await new Promise(async resolve => { - if (MatlabLifecycleManager.isMatlabConnected()) { - resolve(await MatlabLifecycleManager.getMatlabConnection()) + if (this.matlabLifecycleManager.isMatlabConnected()) { + resolve(await this.matlabLifecycleManager.getMatlabConnection()) } else { // MATLAB is not already connected, so wait until it has connected to // resolve the connection. - MatlabLifecycleManager.eventEmitter.once('connected', async () => { - resolve(await MatlabLifecycleManager.getMatlabConnection()) + this.matlabLifecycleManager.eventEmitter.once('connected', async () => { + resolve(await this.matlabLifecycleManager.getMatlabConnection()) }) } }) @@ -172,7 +178,7 @@ class NavigationSupportProvider { return [] } // Ensure document index is up to date - await DocumentIndexer.ensureDocumentIndexIsUpdated(textDocument) + await this.documentIndexer.ensureDocumentIndexIsUpdated(textDocument) const codeData = FileInfoIndex.codeDataCache.get(uri) if (codeData == null) { reportTelemetry(requestType, 'No code data') @@ -405,7 +411,7 @@ class NavigationSupportProvider { * @returns The definition location(s), or null if no definition was found */ private async findDefinitionOnPath (uri: string, position: Position, expression: Expression, matlabConnection: MatlabConnection): Promise { - const resolvedPath = await PathResolver.resolvePaths([expression.targetExpression], uri, matlabConnection) + const resolvedPath = await this.pathResolver.resolvePaths([expression.targetExpression], uri, matlabConnection) const resolvedUri = resolvedPath[0].uri if (resolvedUri === '') { @@ -421,7 +427,7 @@ class NavigationSupportProvider { if (!FileInfoIndex.codeDataCache.has(resolvedUri)) { // Index target file, if necessary - await Indexer.indexFile(resolvedUri) + await this.indexer.indexFile(resolvedUri) } const codeData = FileInfoIndex.codeDataCache.get(resolvedUri) @@ -655,4 +661,4 @@ class NavigationSupportProvider { } } -export default new NavigationSupportProvider() +export default NavigationSupportProvider diff --git a/src/providers/navigation/PathResolver.ts b/src/providers/navigation/PathResolver.ts index 0186d13..d8c63d9 100644 --- a/src/providers/navigation/PathResolver.ts +++ b/src/providers/navigation/PathResolver.ts @@ -64,4 +64,4 @@ class PathResolver { } } -export default new PathResolver() +export default PathResolver diff --git a/src/server.ts b/src/server.ts index 8261723..4bf967f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,7 +1,7 @@ // Copyright 2022 - 2024 The MathWorks, Inc. import { TextDocument } from 'vscode-languageserver-textdocument' -import { ClientCapabilities, createConnection, InitializeParams, InitializeResult, ProposedFeatures, TextDocuments } from 'vscode-languageserver/node' +import { ClientCapabilities, InitializeParams, InitializeResult, TextDocuments } from 'vscode-languageserver/node' import DocumentIndexer from './indexing/DocumentIndexer' import WorkspaceIndexer from './indexing/WorkspaceIndexer' import ConfigurationManager, { ConnectionTiming } from './lifecycle/ConfigurationManager' @@ -9,7 +9,7 @@ import MatlabLifecycleManager from './lifecycle/MatlabLifecycleManager' import Logger from './logging/Logger' import { Actions, reportTelemetryAction } from './logging/TelemetryUtils' import NotificationService, { Notification } from './notifications/NotificationService' -import CompletionProvider from './providers/completion/CompletionSupportProvider' +import CompletionSupportProvider from './providers/completion/CompletionSupportProvider' import FormatSupportProvider from './providers/formatting/FormatSupportProvider' import LintingSupportProvider from './providers/linting/LintingSupportProvider' import ExecuteCommandProvider, { MatlabLSCommands } from './providers/lspCommands/ExecuteCommandProvider' @@ -17,205 +17,221 @@ import NavigationSupportProvider, { RequestType } from './providers/navigation/N import LifecycleNotificationHelper from './lifecycle/LifecycleNotificationHelper' import MVM from './mvm/MVM' import FoldingSupportProvider from './providers/folding/FoldingSupportProvider' -import { FoldingRange } from 'vscode-languageserver' - -// Create a connection for the server -export const connection = createConnection(ProposedFeatures.all) - -// Initialize Logger -Logger.initialize(connection.console) - -// Create basic text document manager -const documentManager: TextDocuments = new TextDocuments(TextDocument) - -let mvm: MVM | null - -MatlabLifecycleManager.eventEmitter.on('connected', () => { - // Handle things after MATLAB® has launched - - // Initiate workspace indexing - void WorkspaceIndexer.indexWorkspace() - - documentManager.all().forEach(textDocument => { - // Lint the open documents - void LintingSupportProvider.lintDocument(textDocument) - - // Index the open document - void DocumentIndexer.indexDocument(textDocument) - }) -}) - -let capabilities: ClientCapabilities - -// Handles an initialization request -connection.onInitialize((params: InitializeParams) => { - capabilities = params.capabilities - - // Defines the capabilities supported by this language server - const initResult: InitializeResult = { - capabilities: { - codeActionProvider: true, - completionProvider: { - triggerCharacters: [ - '.', // Struct/class properties, package names, etc. - '(', // Function call - ' ', // Command-style function call - ',', // Function arguments - '/', // File path - '\\' // File path - ] - }, - definitionProvider: true, - documentFormattingProvider: true, - executeCommandProvider: { - commands: Object.values(MatlabLSCommands) - }, - foldingRangeProvider: true, - referencesProvider: true, - signatureHelpProvider: { - triggerCharacters: ['(', ','] - }, - documentSymbolProvider: true - } - } +import ClientConnection from './ClientConnection' +import PathResolver from './providers/navigation/PathResolver' +import Indexer from './indexing/Indexer' - return initResult -}) +export async function startServer () { + // Create a connection for the server + const connection = ClientConnection.getConnection() -// Handles the initialized notification -connection.onInitialized(() => { - ConfigurationManager.setup(capabilities) + // Instantiate services + const pathResolver = new PathResolver() + const matlabLifecycleManager = new MatlabLifecycleManager() - WorkspaceIndexer.setupCallbacks(capabilities) + const indexer = new Indexer(matlabLifecycleManager, pathResolver) + const workspaceIndexer = new WorkspaceIndexer(indexer) + const documentIndexer = new DocumentIndexer(indexer) - mvm = new MVM(NotificationService, MatlabLifecycleManager); + const formatSupportProvider = new FormatSupportProvider(matlabLifecycleManager) + const foldingSupportProvider = new FoldingSupportProvider(matlabLifecycleManager) + const lintingSupportProvider = new LintingSupportProvider(matlabLifecycleManager) + const executeCommandProvider = new ExecuteCommandProvider(lintingSupportProvider) + const completionSupportProvider = new CompletionSupportProvider(matlabLifecycleManager) + const navigationSupportProvider = new NavigationSupportProvider(matlabLifecycleManager, indexer, documentIndexer, pathResolver) - void startMatlabIfOnStartLaunch() -}) + // Create basic text document manager + const documentManager: TextDocuments = new TextDocuments(TextDocument) -async function startMatlabIfOnStartLaunch (): Promise { - // Launch MATLAB if it should be launched early - const connectionTiming = (await ConfigurationManager.getConfiguration()).matlabConnectionTiming - if (connectionTiming === ConnectionTiming.OnStart) { - void MatlabLifecycleManager.connectToMatlab().catch(reason => { - Logger.error(`MATLAB onStart connection failed: ${reason}`) - }) - } -} + let mvm: MVM | null -// Handles a shutdown request -connection.onShutdown(() => { - // Shut down MATLAB - MatlabLifecycleManager.disconnectFromMatlab() -}) + matlabLifecycleManager.eventEmitter.on('connected', () => { + // Handle things after MATLAB® has launched -interface MatlabConnectionStatusParam { - connectionAction: 'connect' | 'disconnect' -} + // Initiate workspace indexing + void workspaceIndexer.indexWorkspace() + + documentManager.all().forEach(textDocument => { + // Lint the open documents + void lintingSupportProvider.lintDocument(textDocument) -// Set up connection notification listeners -NotificationService.registerNotificationListener( - Notification.MatlabConnectionClientUpdate, - (data: MatlabConnectionStatusParam) => { - switch (data.connectionAction) { - case 'connect': - void MatlabLifecycleManager.connectToMatlab().catch(reason => { - Logger.error(`Connection request failed: ${reason}`) - }) - break - case 'disconnect': - MatlabLifecycleManager.disconnectFromMatlab() + // Index the open document + void documentIndexer.indexDocument(textDocument) + }) + }) + + let capabilities: ClientCapabilities + + // Handles an initialization request + connection.onInitialize((params: InitializeParams) => { + capabilities = params.capabilities + + // Defines the capabilities supported by this language server + const initResult: InitializeResult = { + capabilities: { + codeActionProvider: true, + completionProvider: { + triggerCharacters: [ + '.', // Struct/class properties, package names, etc. + '(', // Function call + ' ', // Command-style function call + ',', // Function arguments + '/', // File path + '\\' // File path + ] + }, + definitionProvider: true, + documentFormattingProvider: true, + executeCommandProvider: { + commands: Object.values(MatlabLSCommands) + }, + foldingRangeProvider: true, + referencesProvider: true, + signatureHelpProvider: { + triggerCharacters: ['(', ','] + }, + documentSymbolProvider: true + } } - } -) - -// Set up MATLAB startup request listener -NotificationService.registerNotificationListener( - Notification.MatlabRequestInstance, - async () => { // eslint-disable-line @typescript-eslint/no-misused-promises - const matlabConnection = await MatlabLifecycleManager.getMatlabConnection(true); - if (matlabConnection === null) { - LifecycleNotificationHelper.notifyMatlabRequirement() + + return initResult + }) + + // Handles the initialized notification + connection.onInitialized(() => { + ConfigurationManager.setup(capabilities) + + workspaceIndexer.setupCallbacks(capabilities) + + mvm = new MVM(NotificationService, matlabLifecycleManager); + + void startMatlabIfOnStartLaunch() + }) + + async function startMatlabIfOnStartLaunch (): Promise { + // Launch MATLAB if it should be launched early + const connectionTiming = (await ConfigurationManager.getConfiguration()).matlabConnectionTiming + if (connectionTiming === ConnectionTiming.OnStart) { + void matlabLifecycleManager.connectToMatlab().catch(reason => { + Logger.error(`MATLAB onStart connection failed: ${reason}`) + }) } } -) - -// Handles files opened -documentManager.onDidOpen(params => { - reportFileOpened(params.document) - void LintingSupportProvider.lintDocument(params.document) - void DocumentIndexer.indexDocument(params.document) -}) - -documentManager.onDidClose(params => { - LintingSupportProvider.clearDiagnosticsForDocument(params.document) -}) - -// Handles files saved -documentManager.onDidSave(params => { - void LintingSupportProvider.lintDocument(params.document) -}) - -// Handles changes to the text document -documentManager.onDidChangeContent(params => { - if (MatlabLifecycleManager.isMatlabConnected()) { - // Only want to lint on content changes when linting is being backed by MATLAB - LintingSupportProvider.queueLintingForDocument(params.document) - DocumentIndexer.queueIndexingForDocument(params.document) + + // Handles a shutdown request + connection.onShutdown(() => { + // Shut down MATLAB + matlabLifecycleManager.disconnectFromMatlab() + }) + + interface MatlabConnectionStatusParam { + connectionAction: 'connect' | 'disconnect' } -}) - -// Handle execute command requests -connection.onExecuteCommand(params => { - void ExecuteCommandProvider.handleExecuteCommand(params, documentManager) -}) - -/** -------------------- COMPLETION SUPPORT -------------------- **/ -connection.onCompletion(async params => { - // Gather a list of possible completions to be displayed by the IDE - return await CompletionProvider.handleCompletionRequest(params, documentManager) -}) - -connection.onSignatureHelp(async params => { - // Gather a list of possible function signatures to be displayed by the IDE - return await CompletionProvider.handleSignatureHelpRequest(params, documentManager) -}) - -/** -------------------- FOLDING SUPPORT -------------------- **/ -connection.onFoldingRanges(async params => { - // Retrieve the folding ranges - // If there are valid folding ranges, hand them back to the IDE - // Else, return null, so the IDE falls back to indent-based folding - return await FoldingSupportProvider.handleFoldingRangeRequest(params, documentManager) -}) - -/** -------------------- FORMATTING SUPPORT -------------------- **/ -connection.onDocumentFormatting(async params => { - // Gather a set of document edits required for formatting, which the IDE will execute - return await FormatSupportProvider.handleDocumentFormatRequest(params, documentManager) -}) - -/** -------------------- LINTING SUPPORT -------------------- **/ -connection.onCodeAction(params => { - // Retrieve a list of possible code actions to be displayed by the IDE - return LintingSupportProvider.handleCodeActionRequest(params) -}) - -/** -------------------- NAVIGATION SUPPORT -------------------- **/ -connection.onDefinition(async params => { - return await NavigationSupportProvider.handleDefOrRefRequest(params, documentManager, RequestType.Definition) -}) - -connection.onReferences(async params => { - return await NavigationSupportProvider.handleDefOrRefRequest(params, documentManager, RequestType.References) -}) - -connection.onDocumentSymbol(async params => { - return await NavigationSupportProvider.handleDocumentSymbol(params, documentManager, RequestType.DocumentSymbol) -}) - -// Start listening to open/change/close text document events -documentManager.listen(connection) + + // Set up connection notification listeners + NotificationService.registerNotificationListener( + Notification.MatlabConnectionClientUpdate, + (data: MatlabConnectionStatusParam) => { + switch (data.connectionAction) { + case 'connect': + void matlabLifecycleManager.connectToMatlab().catch(reason => { + Logger.error(`Connection request failed: ${reason}`) + }) + break + case 'disconnect': + matlabLifecycleManager.disconnectFromMatlab() + } + } + ) + + // Set up MATLAB startup request listener + NotificationService.registerNotificationListener( + Notification.MatlabRequestInstance, + async () => { // eslint-disable-line @typescript-eslint/no-misused-promises + const matlabConnection = await matlabLifecycleManager.getMatlabConnection(true); + if (matlabConnection === null) { + LifecycleNotificationHelper.notifyMatlabRequirement() + } + } + ) + + // Handles files opened + documentManager.onDidOpen(params => { + reportFileOpened(params.document) + void lintingSupportProvider.lintDocument(params.document) + void documentIndexer.indexDocument(params.document) + }) + + documentManager.onDidClose(params => { + lintingSupportProvider.clearDiagnosticsForDocument(params.document) + }) + + // Handles files saved + documentManager.onDidSave(params => { + void lintingSupportProvider.lintDocument(params.document) + }) + + // Handles changes to the text document + documentManager.onDidChangeContent(params => { + if (matlabLifecycleManager.isMatlabConnected()) { + // Only want to lint on content changes when linting is being backed by MATLAB + lintingSupportProvider.queueLintingForDocument(params.document) + documentIndexer.queueIndexingForDocument(params.document) + } + }) + + // Handle execute command requests + connection.onExecuteCommand(params => { + void executeCommandProvider.handleExecuteCommand(params, documentManager) + }) + + /** -------------------- COMPLETION SUPPORT -------------------- **/ + connection.onCompletion(async params => { + // Gather a list of possible completions to be displayed by the IDE + return await completionSupportProvider.handleCompletionRequest(params, documentManager) + }) + + connection.onSignatureHelp(async params => { + // Gather a list of possible function signatures to be displayed by the IDE + return await completionSupportProvider.handleSignatureHelpRequest(params, documentManager) + }) + + /** -------------------- FOLDING SUPPORT -------------------- **/ + connection.onFoldingRanges(async params => { + // Retrieve the folding ranges + // If there are valid folding ranges, hand them back to the IDE + // Else, return null, so the IDE falls back to indent-based folding + return await foldingSupportProvider.handleFoldingRangeRequest(params, documentManager) + }) + + /** -------------------- FORMATTING SUPPORT -------------------- **/ + connection.onDocumentFormatting(async params => { + // Gather a set of document edits required for formatting, which the IDE will execute + return await formatSupportProvider.handleDocumentFormatRequest(params, documentManager) + }) + + /** -------------------- LINTING SUPPORT -------------------- **/ + connection.onCodeAction(params => { + // Retrieve a list of possible code actions to be displayed by the IDE + return lintingSupportProvider.handleCodeActionRequest(params) + }) + + /** -------------------- NAVIGATION SUPPORT -------------------- **/ + connection.onDefinition(async params => { + return await navigationSupportProvider.handleDefOrRefRequest(params, documentManager, RequestType.Definition) + }) + + connection.onReferences(async params => { + return await navigationSupportProvider.handleDefOrRefRequest(params, documentManager, RequestType.References) + }) + + connection.onDocumentSymbol(async params => { + return await navigationSupportProvider.handleDocumentSymbol(params, documentManager, RequestType.DocumentSymbol) + }) + + // Start listening to open/change/close text document events + documentManager.listen(connection) +} /** -------------------- Helper Functions -------------------- **/ function reportFileOpened (document: TextDocument): void {