Skip to content

Commit 4886ff0

Browse files
authored
Merge pull request #61 from mathworks/dklilley/release/1.3.3
MATLAB language server - v1.3.3
2 parents 4765aa2 + afcef80 commit 4886ff0

File tree

20 files changed

+287
-62
lines changed

20 files changed

+287
-62
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ A clear and concise description of what you expected to happen.
2424
If applicable, add screenshots to help explain your problem.
2525

2626
**Useful Information**
27+
- MATLAB Version:
2728
- OS Version:
2829
- Language Server Client: [e.g. MATLAB extension for Visual Studio Code]
2930
- Client Version:

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ MATLAB language server supports these editors by installing the corresponding ex
2626

2727
### Unreleased
2828

29+
### 1.3.3
30+
Release date: 2025-05-15
31+
32+
Added:
33+
* Support for debugging P-coded files when the corresponding source file is available
34+
35+
Fixed:
36+
* Resolves potential crashes when using code completion in files without a .m file extension
37+
2938
### 1.3.2
3039
Release date: 2025-03-06
3140

matlab/+matlabls/+handlers/+completions/getCompletions.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
% GETCOMPLETIONS Retrieves the data for the possible completions at the cursor position in the given code.
33

44
% Copyright 2025 The MathWorks, Inc.
5+
[~, ~, ext] = fileparts(fileName);
6+
if ~isempty(fileName) && ~strcmpi(ext, '.m')
7+
% Expected .m file extension
8+
error('MATLAB:vscode:invalidFileExtension', 'The provided file must have a .m extension to process completions.');
9+
end
510

611
completionResultsStr = matlabls.internal.getCompletionsData(code, fileName, cursorPosition);
712
completionsData = filterCompletionResults(completionResultsStr);

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "matlab-language-server",
3-
"version": "1.3.2",
3+
"version": "1.3.3",
44
"description": "Language Server for MATLAB code",
55
"main": "./src/index.ts",
66
"bin": "./out/index.js",

src/debug/MatlabDebugAdaptor.ts

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DebugProtocol } from '@vscode/debugprotocol';
55
import { DebugServices, BreakpointInfo } from './DebugServices'
66
import { ResolvablePromise, createResolvablePromise } from '../utils/PromiseUtils'
77
import { IMVM, MVMError, MatlabState } from '../mvm/impl/MVM';
8+
import fs from 'node:fs';
89

910
enum BreakpointChangeType {
1011
ADD,
@@ -233,6 +234,8 @@ export default class MatlabDebugAdaptor {
233234

234235
private _setupListeners (): void {
235236
this._debugServices.on(DebugServices.Events.BreakpointAdded, async (breakpoint: BreakpointInfo) => {
237+
breakpoint.filePath = this._mapToMFile(breakpoint.filePath, false);
238+
236239
this._matlabBreakpoints.push(breakpoint);
237240

238241
this._breakpointChangeListeners.forEach((listener) => {
@@ -241,6 +244,8 @@ export default class MatlabDebugAdaptor {
241244
});
242245

243246
this._debugServices.on(DebugServices.Events.BreakpointRemoved, async (breakpoint: BreakpointInfo) => {
247+
breakpoint.filePath = this._mapToMFile(breakpoint.filePath, false);
248+
244249
this._matlabBreakpoints = this._matlabBreakpoints.filter((existingBreakpoint) => {
245250
return !existingBreakpoint.equals(breakpoint, true);
246251
});
@@ -422,6 +427,7 @@ export default class MatlabDebugAdaptor {
422427
}
423428

424429
const canonicalizedPath = await this._getCanonicalPath(source.path);
430+
const pathToSetOrClear = this._mapToPFile(canonicalizedPath, true);
425431

426432
const newBreakpoints: BreakpointInfo[] = (args.breakpoints != null)
427433
? args.breakpoints.map((breakpoint) => {
@@ -458,7 +464,7 @@ export default class MatlabDebugAdaptor {
458464
// Remove all breakpoints that are now gone.
459465
const breakpointsRemovalPromises: Array<Promise<void>> = [];
460466
breakpointsToRemove.forEach((breakpoint: BreakpointInfo) => {
461-
breakpointsRemovalPromises.push(this._mvm.clearBreakpoint(breakpoint.filePath, breakpoint.lineNumber));
467+
breakpointsRemovalPromises.push(this._mvm.clearBreakpoint(pathToSetOrClear, breakpoint.lineNumber));
462468
})
463469
await Promise.all(breakpointsRemovalPromises);
464470

@@ -476,12 +482,12 @@ export default class MatlabDebugAdaptor {
476482

477483
let matlabBreakpointInfos: BreakpointInfo[] = [];
478484
const listener = this._registerBreakpointChangeListener((changeType, bpInfo) => {
479-
if (changeType === BreakpointChangeType.ADD && bpInfo.filePath === canonicalizedPath) {
485+
if (changeType === BreakpointChangeType.ADD && bpInfo.filePath === pathToSetOrClear) {
480486
matlabBreakpointInfos.push(bpInfo);
481487
}
482488
});
483489

484-
await this._mvm.setBreakpoint(canonicalizedPath, newBreakpoint.info.lineNumber, newBreakpoint.info.condition);
490+
await this._mvm.setBreakpoint(pathToSetOrClear, newBreakpoint.info.lineNumber, newBreakpoint.info.condition);
485491

486492
listener.remove();
487493

@@ -501,6 +507,36 @@ export default class MatlabDebugAdaptor {
501507
this._clearPendingBreakpointsRequest();
502508
}
503509

510+
_mapToPFile (filePath: string, checkIfExists: boolean): string {
511+
// If this is an m-file then convert to p-file and check existence
512+
if (filePath.endsWith('.m')) {
513+
const pFile = filePath.substring(0, filePath.length - 1) + 'p';
514+
if (!checkIfExists || fs.existsSync(pFile)) {
515+
return pFile;
516+
} else {
517+
return filePath;
518+
}
519+
}
520+
521+
// Not an m file so p-code not supported
522+
return filePath;
523+
}
524+
525+
_mapToMFile (filePath: string, checkIfExists: boolean): string {
526+
// If this is an p-file then convert to m-file and check existence
527+
if (filePath.endsWith('.p')) {
528+
const mFile = filePath.substring(0, filePath.length - 1) + 'm';
529+
if (!checkIfExists || fs.existsSync(mFile)) {
530+
return mFile;
531+
} else {
532+
return filePath;
533+
}
534+
}
535+
536+
// Not an m file so p-code not supported
537+
return filePath;
538+
}
539+
504540
async continueRequest (response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments, request?: DebugProtocol.Request): Promise<void> {
505541
try {
506542
await this._mvm.eval("if system_dependent('IsDebugMode')==1, dbcont; end");
@@ -558,15 +594,18 @@ export default class MatlabDebugAdaptor {
558594
if (stack[0]?.mwtype !== undefined) {
559595
stack = stack[0]
560596
const size = stack.mwsize[0];
561-
const newStack = [];
597+
const transformedStack = [];
562598
for (let i = 0; i < size; i++) {
563-
newStack.push(new debug.StackFrame(size - i + 1, stack.mwdata.name[i], new debug.Source(stack.mwdata.name[i], stack.mwdata.file[i]), Math.abs(stack.mwdata.line[i]), 1))
599+
transformedStack.push({ name: stack.mwdata.name[i], file: stack.mwdata.file[i], line: stack.mwdata.line[i] });
564600
}
565-
return newStack;
566-
} else {
567-
const numberOfStackFrames: number = stack.length;
568-
return stack.map((stackFrame: MatlabData, i: number) => new debug.StackFrame(numberOfStackFrames - i + 1, stackFrame.name, new debug.Source(stackFrame.name as string, stackFrame.file as string), Math.abs(stackFrame.line), 1));
601+
stack = transformedStack;
569602
}
603+
604+
const numberOfStackFrames: number = stack.length;
605+
return stack.map((stackFrame: MatlabData, i: number) => {
606+
const fileName: string = this._mapToMFile(stackFrame.file, true);
607+
return new debug.StackFrame(numberOfStackFrames - i + 1, stackFrame.name, new debug.Source(stackFrame.name as string, fileName), Math.abs(stackFrame.line), 1)
608+
});
570609
};
571610

572611
const stack = transformStack(stackResponse.result);

src/indexing/Indexer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import ConfigurationManager from '../lifecycle/ConfigurationManager'
1010
import MVM from '../mvm/impl/MVM'
1111
import Logger from '../logging/Logger'
1212
import parse from '../mvm/MdaParser'
13+
import * as FileNameUtils from '../utils/FileNameUtils'
1314

1415
interface WorkspaceFileIndexedResponse {
1516
isDone: boolean
@@ -115,7 +116,8 @@ export default class Indexer {
115116
return
116117
}
117118

118-
const fileContentBuffer = await fs.readFile(URI.parse(uri).fsPath)
119+
const filePath = FileNameUtils.getFilePathFromUri(uri)
120+
const fileContentBuffer = await fs.readFile(filePath)
119121
const code = fileContentBuffer.toString()
120122
const rawCodeData = await this.getCodeData(code, uri)
121123

@@ -136,7 +138,7 @@ export default class Indexer {
136138
* @returns The raw data extracted from the document
137139
*/
138140
private async getCodeData (code: string, uri: string): Promise<RawCodeData | null> {
139-
const filePath = URI.parse(uri).fsPath
141+
const filePath = FileNameUtils.getFilePathFromUri(uri)
140142
const analysisLimit = (await ConfigurationManager.getConfiguration()).maxFileSizeForAnalysis
141143

142144
try {

src/indexing/SymbolSearchService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import Expression from '../utils/ExpressionUtils'
88
import { getTextOnLine } from '../utils/TextDocumentUtils'
99
import PathResolver from '../providers/navigation/PathResolver'
1010
import * as fs from 'fs/promises'
11-
import { URI } from 'vscode-uri'
1211
import Indexer from './Indexer'
12+
import * as FileNameUtils from '../utils/FileNameUtils'
1313

1414
export enum RequestType {
1515
Definition,
@@ -321,7 +321,7 @@ class SymbolSearchService {
321321
}
322322

323323
// Ensure URI is not a directory. This can occur with some packages.
324-
const fileStats = await fs.stat(URI.parse(resolvedUri).fsPath)
324+
const fileStats = await fs.stat(FileNameUtils.getFilePathFromUri(resolvedUri))
325325
if (fileStats.isDirectory()) {
326326
return null
327327
}

src/lifecycle/MatlabCommunicationManager.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lifecycle/PathSynchronizer.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import Logger from '../logging/Logger'
66
import MatlabLifecycleManager from './MatlabLifecycleManager'
77
import * as os from 'os'
88
import path from 'path'
9-
import { URI } from 'vscode-uri'
109
import MVM, { IMVM, MatlabState } from '../mvm/impl/MVM'
1110
import parse from '../mvm/MdaParser'
11+
import * as FileNameUtils from '../utils/FileNameUtils'
1212

1313
export default class PathSynchronizer {
1414
constructor (private readonly matlabLifecycleManager: MatlabLifecycleManager, private readonly mvm: MVM) {}
@@ -161,9 +161,7 @@ export default class PathSynchronizer {
161161

162162
private convertWorkspaceFoldersToFilePaths (workspaceFolders: WorkspaceFolder[]): string[] {
163163
return workspaceFolders.map(folder => {
164-
const uri = URI.parse(folder.uri)
165-
166-
return path.normalize(uri.fsPath)
164+
return path.normalize(FileNameUtils.getFilePathFromUri(folder.uri))
167165
});
168166
}
169167

0 commit comments

Comments
 (0)