Skip to content

Commit 9bdf3d2

Browse files
philipb314GitHub Enterprise
authored andcommitted
Add new ChangeStack request and update logic to handle not being at t… (mathworks#57)
* Add new ChangeStack request and update logic to handle not being at the top of the stack. * Update server to fix all eslint issues. * Fixed something I meant to revert
1 parent 330c5e6 commit 9bdf3d2

21 files changed

+260
-130
lines changed

src/debug/DebugServices.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 The MathWorks, Inc.
1+
// Copyright 2024-2025 The MathWorks, Inc.
22

33
import { IMVM } from '../mvm/impl/MVM'
44
import EventEmitter from 'events';
@@ -78,7 +78,7 @@ enum Events {
7878

7979
export class DebugServices extends EventEmitter {
8080
static Events = Events;
81-
private _mvm: IMVM;
81+
private readonly _mvm: IMVM;
8282

8383
constructor (mvm: IMVM) {
8484
super();
@@ -93,10 +93,9 @@ export class DebugServices extends EventEmitter {
9393
this._mvm.on('ContinueExecutionEvent', (data: MatlabData) => {
9494
this.emit(DebugServices.Events.DBCont);
9595
});
96-
this._mvm.on('ChangeCurrentWorkspace', (data: MatlabData) => {
96+
this._mvm.on('ChangeCurrentWorkspaceEvent', (data: MatlabData) => {
9797
this.emit(DebugServices.Events.DBWorkspaceChanged);
9898
});
99-
10099
this._mvm.on('AddLineNumberBreakpointEvent', (data: MatlabData) => {
101100
this.emit(DebugServices.Events.BreakpointAdded, new BreakpointInfo(data.Filespec, data.LineNumber, data.Condition, data.whichAnonymousFunctionOnCurrentLine));
102101
});

src/debug/MatlabDebugAdaptor.ts

Lines changed: 149 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,23 @@ const isError = function <T> (value: T | MVMError): boolean {
6060

6161
export default class MatlabDebugAdaptor {
6262
static _nextId = 1;
63-
private _debugServices: DebugServices;
64-
private _mvm: IMVM;
63+
private readonly _debugServices: DebugServices;
64+
private readonly _mvm: IMVM;
6565

6666
private _numberOfStackFrames: number = -1;
67+
private _currentMATLABFrame: number = -1;
68+
private _pendingStackPromise?: ResolvablePromise<void>;
69+
private _followUpStackRequested: boolean = false;
70+
private readonly _ignoreWorkspaceUpdates: boolean = false;
6771

6872
private _pendingSetBreakpointPromise?: ResolvablePromise<void>;
69-
private _pendingVariablesPromise?: ResolvablePromise<void>;
73+
private _pendingTemporaryStackChangePromise?: ResolvablePromise<void>;
7074

7175
private _breakpointChangeListeners: Array<(type: BreakpointChangeType, bp: BreakpointInfo) => void> = [];
7276

7377
private _matlabBreakpoints: BreakpointInfo[] = [];
7478

75-
private _canonicalizedPathCache: Map<string, ResolvablePromise<string>> = new Map();
79+
private readonly _canonicalizedPathCache: Map<string, ResolvablePromise<string>> = new Map();
7680

7781
private _isCurrentlyStopped: boolean = false;
7882
protected _isCurrentlyDebugging: boolean = false;
@@ -84,7 +88,7 @@ export default class MatlabDebugAdaptor {
8488
this._debugServices = debugServices;
8589

8690
this._pendingSetBreakpointPromise = undefined;
87-
this._pendingVariablesPromise = undefined;
91+
this._pendingTemporaryStackChangePromise = undefined;
8892

8993
this._mvm.on(IMVM.Events.stateChange, (state: MatlabState) => {
9094
if (state === MatlabState.DISCONNECTED) {
@@ -203,11 +207,15 @@ export default class MatlabDebugAdaptor {
203207
this._pendingSetBreakpointPromise = createResolvablePromise();
204208
}
205209

206-
private async _waitForPendingVariablesRequest (): Promise<void> {
207-
while (this._pendingVariablesPromise !== undefined) {
208-
await this._pendingVariablesPromise;
210+
private async _waitForPendingStackChanges (createNewPendingChange: boolean = true): Promise<void> {
211+
while (this._pendingTemporaryStackChangePromise !== undefined) {
212+
await this._pendingTemporaryStackChangePromise;
213+
}
214+
this._pendingTemporaryStackChangePromise = undefined;
215+
216+
if (createNewPendingChange) {
217+
this._pendingTemporaryStackChangePromise = createResolvablePromise();
209218
}
210-
this._pendingVariablesPromise = createResolvablePromise();
211219
}
212220

213221
private _clearPendingBreakpointsRequest (): void {
@@ -216,9 +224,9 @@ export default class MatlabDebugAdaptor {
216224
oldPromise?.resolve();
217225
}
218226

219-
private _clearPendingVariablesRequest (): void {
220-
const oldPromise = this._pendingVariablesPromise;
221-
this._pendingVariablesPromise = undefined;
227+
private _clearPendingStackChanges (): void {
228+
const oldPromise = this._pendingTemporaryStackChangePromise;
229+
this._pendingTemporaryStackChangePromise = undefined;
222230
oldPromise?.resolve();
223231
}
224232

@@ -280,8 +288,32 @@ export default class MatlabDebugAdaptor {
280288
this._handleDebuggingStateChange();
281289
}
282290

291+
this._currentMATLABFrame = stack.length;
292+
293+
void this._requestStackUpdate();
294+
295+
this.sendEvent(new debug.StoppedEvent('breakpoint', 0));
296+
});
297+
298+
this._debugServices.on(DebugServices.Events.DBStop, async (filename: string, lineNumber: number, stack: MatlabData[]) => {
299+
this._isCurrentlyStopped = true;
300+
301+
const oldValue = this._isCurrentlyDebugging;
302+
this._isCurrentlyDebugging = true;
303+
if (oldValue !== this._isCurrentlyDebugging) {
304+
this._handleDebuggingStateChange();
305+
}
306+
307+
this._currentMATLABFrame = stack.length;
308+
283309
this.sendEvent(new debug.StoppedEvent('breakpoint', 0));
284310
});
311+
312+
this._debugServices.on(DebugServices.Events.DBWorkspaceChanged, () => {
313+
if (!this._ignoreWorkspaceUpdates) {
314+
void this._requestStackUpdate();
315+
}
316+
});
285317
}
286318

287319
protected _handleDebuggingStateChange (): void {
@@ -527,7 +559,6 @@ export default class MatlabDebugAdaptor {
527559
const size = stack.mwsize[0];
528560
const newStack = [];
529561
for (let i = 0; i < size; i++) {
530-
531562
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))
532563
}
533564
return newStack;
@@ -630,7 +661,6 @@ export default class MatlabDebugAdaptor {
630661
}
631662

632663
async evaluateRequest (response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments, request?: DebugProtocol.Request): Promise<void> {
633-
634664
let stackChanger;
635665
try {
636666
stackChanger = await this._moveToFrame(args.frameId);
@@ -653,7 +683,7 @@ export default class MatlabDebugAdaptor {
653683
maybeResult = await this._mvm.feval<MatlabData>('evalc', 1, ["try, datatipinfo('" + args.expression + "'), catch, disp('Error evaluating expression'); end"]);
654684
}
655685

656-
await this._mvm.feval('feature', 0, ['HotLinks', ((oldHotlinks as any)?.result?.[0] ?? true)]);
686+
await this._mvm.feval('feature', 0, ['HotLinks', ((oldHotlinks as MatlabData)?.result?.[0] ?? true)]);
657687

658688
if (stackChanger !== null) {
659689
try {
@@ -679,8 +709,8 @@ export default class MatlabDebugAdaptor {
679709
this.sendResponse(response);
680710
return;
681711
}
682-
response.body = {
683-
result: result,
712+
response.body = {
713+
result,
684714
variablesReference: 0
685715
};
686716
response.body.type = 'string';
@@ -700,20 +730,24 @@ export default class MatlabDebugAdaptor {
700730
protected customRequest (command: string, response: DebugProtocol.Response, args: any, request?: DebugProtocol.Request): void {
701731
if (command === 'cacheFilePath') {
702732
this._getCanonicalPath(args.fileName).then(() => { }, () => { });
733+
} else if (command === 'StackChange') {
734+
void this._requestStackChange(args.frame);
703735
}
704736
this.sendResponse(response);
705737
}
706738

707739
private _cleanup (): void {
708740
this._numberOfStackFrames = -1;
709-
if (this._pendingSetBreakpointPromise != null) {
710-
this._pendingSetBreakpointPromise.reject();
711-
this._pendingSetBreakpointPromise = undefined;
712-
}
713-
if (this._pendingVariablesPromise != null) {
714-
this._pendingVariablesPromise.reject();
715-
this._pendingVariablesPromise = undefined;
716-
}
741+
742+
this._pendingStackPromise?.reject();
743+
this._pendingStackPromise = undefined;
744+
this._followUpStackRequested = false;
745+
746+
this._pendingSetBreakpointPromise?.reject();
747+
this._pendingSetBreakpointPromise = undefined;
748+
this._pendingTemporaryStackChangePromise?.reject();
749+
this._pendingTemporaryStackChangePromise = undefined;
750+
717751
this._breakpointChangeListeners = [];
718752
}
719753

@@ -730,12 +764,18 @@ export default class MatlabDebugAdaptor {
730764
}
731765

732766
try {
733-
await this._waitForPendingVariablesRequest();
767+
await this._waitForPendingStackChanges();
734768
} catch (e) {
735769
return null;
736770
}
737771

738-
const dbAmount = this._numberOfStackFrames - frameId;
772+
try {
773+
await this._waitForStack();
774+
} catch (e) {
775+
return null;
776+
}
777+
778+
const dbAmount = (this._numberOfStackFrames - this._currentMATLABFrame + 1) - frameId;
739779
if (dbAmount !== 0) {
740780
try {
741781
if (dbAmount > 0) {
@@ -744,14 +784,20 @@ export default class MatlabDebugAdaptor {
744784
await this._mvm.feval<undefined>('dbdown', 0, [-dbAmount]);
745785
}
746786
} catch (e) {
747-
this._clearPendingVariablesRequest();
787+
this._clearPendingStackChanges();
748788
throw e;
749789
}
750790
}
751791

752792
return {
753793
revert: async () => {
754-
const dbAmount = this._numberOfStackFrames - frameId;
794+
try {
795+
await this._waitForStack();
796+
} catch (e) {
797+
return
798+
}
799+
800+
const dbAmount = (this._numberOfStackFrames - this._currentMATLABFrame + 1) - frameId;
755801
if (dbAmount !== 0) {
756802
try {
757803
if (dbAmount > 0) {
@@ -760,16 +806,88 @@ export default class MatlabDebugAdaptor {
760806
await this._mvm.feval<undefined>('dbup', 0, [-dbAmount]);
761807
}
762808
} catch (e) {
763-
this._clearPendingVariablesRequest();
809+
this._clearPendingStackChanges();
764810
return;
765811
}
766812
}
767813

768-
this._clearPendingVariablesRequest();
814+
this._clearPendingStackChanges();
769815
}
770816
};
771817
}
772818

819+
private async _requestStackChange (frameId: number): Promise<void> {
820+
if (frameId === undefined) {
821+
return;
822+
}
823+
824+
try {
825+
await this._waitForPendingStackChanges();
826+
} catch (e) {
827+
return;
828+
}
829+
830+
try {
831+
await this._waitForStack();
832+
} catch (e) {
833+
return;
834+
}
835+
836+
const dbAmount = (this._numberOfStackFrames - this._currentMATLABFrame + 1) - frameId;
837+
if (dbAmount !== 0) {
838+
try {
839+
if (dbAmount > 0) {
840+
await this._mvm.feval<undefined>('dbup', 0, [dbAmount]);
841+
} else {
842+
await this._mvm.feval<undefined>('dbdown', 0, [-dbAmount]);
843+
}
844+
} catch (e) {
845+
}
846+
}
847+
848+
this._clearPendingStackChanges();
849+
}
850+
851+
private async _waitForStack (): Promise<void> {
852+
if (this._pendingStackPromise != null) {
853+
await this._pendingStackPromise;
854+
}
855+
}
856+
857+
private _requestStackUpdate (): Promise<void> {
858+
if (this._pendingStackPromise != null) {
859+
this._followUpStackRequested = true;
860+
return this._pendingStackPromise;
861+
}
862+
863+
this._pendingStackPromise = createResolvablePromise();
864+
865+
const requestStackHelper = (): void => {
866+
this._mvm.feval('dbstack', 2, []).then((maybeResult: MatlabData) => {
867+
if (isError(maybeResult)) {
868+
console.error(maybeResult.error);
869+
return;
870+
}
871+
const result = maybeResult.result;
872+
this._currentMATLABFrame = result[1];
873+
874+
if (this._followUpStackRequested) {
875+
this._followUpStackRequested = false;
876+
requestStackHelper();
877+
} else {
878+
this._pendingStackPromise?.resolve();
879+
this._pendingStackPromise = undefined;
880+
}
881+
}, (err: MatlabData) => {
882+
console.error(err);
883+
});
884+
}
885+
886+
requestStackHelper();
887+
888+
return this._pendingStackPromise;
889+
}
890+
773891
private async _getCanonicalPath (path: string): Promise<string> {
774892
let cachePromise: ResolvablePromise<string> | undefined = this._canonicalizedPathCache.get(path);
775893

src/debug/MatlabDebugAdaptorServer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ interface TaggedData {
2525
}
2626

2727
export default class MatlabDebugAdaptorServer extends MatlabDebugAdaptor {
28-
private _notifier: typeof NotificationService;
28+
private readonly _notifier: typeof NotificationService;
2929

3030
constructor (mvm: IMVM, debugServices: DebugServices) {
3131
super(mvm, debugServices);

src/indexing/FileInfoIndex.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export interface RawCodeData {
1515
errorInfo: CodeDataErrorInfo | undefined
1616
}
1717

18-
1918
interface CodeDataErrorInfo {
2019
message: string
2120
}
@@ -392,8 +391,9 @@ export class MatlabCodeData {
392391
this.parseSectionInfo(rawCodeData.sections)
393392
this.parseErrorInfo(rawCodeData.errorInfo)
394393
}
395-
parseErrorInfo (errorInfo: CodeDataErrorInfo | undefined) {
396-
if(errorInfo == undefined) {
394+
395+
parseErrorInfo (errorInfo: CodeDataErrorInfo | undefined): void {
396+
if (errorInfo === undefined) {
397397
this.errorMessage = undefined
398398
return
399399
}

src/indexing/Indexer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export default class Indexer {
110110
private async getCodeData (code: string, uri: string, matlabConnection: MatlabConnection): Promise<RawCodeData> {
111111
const filePath = URI.parse(uri).fsPath
112112

113+
// eslint-disable-next-line no-async-promise-executor
113114
return await new Promise(async resolve => {
114115
const channelId = matlabConnection.getChannelId()
115116
const channel = `${this.INDEX_DOCUMENT_RESPONSE_CHANNEL}/${channelId}`

0 commit comments

Comments
 (0)