-
Notifications
You must be signed in to change notification settings - Fork 348
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add script editor debugger - experimental (#2087)
Co-authored-by: Alan Chin <akchin@us.ibm.com>
- Loading branch information
1 parent
3cec17f
commit e0e2e32
Showing
31 changed files
with
1,171 additions
and
557 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
{ | ||
"name": "@elyra/script-debugger-extension", | ||
"version": "3.10.0-dev", | ||
"description": "JupyterLab extension - visual debugging support for script editors", | ||
"keywords": [ | ||
"jupyter", | ||
"jupyterlab", | ||
"jupyterlab-extension" | ||
], | ||
"homepage": "https://github.com/elyra-ai/elyra", | ||
"bugs": { | ||
"url": "https://github.com/elyra-ai/elyra/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/elyra-ai/elyra/" | ||
}, | ||
"license": "Apache-2.0", | ||
"main": "lib/index.js", | ||
"types": "lib/index.d.ts", | ||
"style": "style/index.css", | ||
"files": [ | ||
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", | ||
"src/**/*.{ts,tsx}", | ||
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}" | ||
], | ||
"scripts": { | ||
"build": "jlpm run build:lib && jlpm run build:labextension:dev", | ||
"build:prod": "jlpm run build:lib && jlpm run build:labextension", | ||
"build:lib": "tsc", | ||
"build:labextension": "jupyter labextension build .", | ||
"build:labextension:dev": "jupyter labextension build --development True .", | ||
"clean": "rimraf lib tsconfig.tsbuildinfo ../../dist/labextensions/@elyra/script-debugger-extension", | ||
"lab:dev": "jupyter labextension develop --overwrite ../../dist/labextensions/@elyra/script-debugger-extension", | ||
"dist": "npm pack .", | ||
"prepare": "npm run build", | ||
"watch": "run-p watch:src watch:labextension", | ||
"watch:src": "tsc -w", | ||
"watch:labextension": "jupyter labextension watch .", | ||
"lab:install": "jupyter labextension install --no-build", | ||
"lab:uninstall": "jupyter labextension uninstall --no-build", | ||
"link:dev": "yarn link @jupyterlab/builder", | ||
"unlink:dev": "yarn unlink @jupyterlab/builder" | ||
}, | ||
"dependencies": { | ||
"@elyra/script-editor": "3.11.0-dev", | ||
"@jupyterlab/application": "^3.4.0", | ||
"@jupyterlab/debugger": "^3.4.0", | ||
"@jupyterlab/fileeditor": "^3.4.0", | ||
"@jupyterlab/services": "^6.4.0", | ||
"@lumino/widgets": "^1.19.0" | ||
}, | ||
"devDependencies": { | ||
"@jupyterlab/builder": "^3.4.0", | ||
"rimraf": "^3.0.2", | ||
"typescript": "~4.1.3" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"jupyterlab": { | ||
"extension": true, | ||
"outputDir": "../../dist/labextensions/@elyra/script-debugger-extension" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
/* | ||
* Copyright 2018-2022 Elyra Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import { ScriptEditor } from '@elyra/script-editor'; | ||
import { | ||
ILabShell, | ||
JupyterFrontEnd, | ||
JupyterFrontEndPlugin | ||
} from '@jupyterlab/application'; | ||
import { Debugger, IDebugger } from '@jupyterlab/debugger'; | ||
import { IEditorTracker } from '@jupyterlab/fileeditor'; | ||
import { KernelManager, Session, SessionManager } from '@jupyterlab/services'; | ||
import { Widget } from '@lumino/widgets'; | ||
|
||
/** | ||
* Debugger plugin. | ||
* Adapted from JupyterLab debugger extension. | ||
* A plugin that provides visual debugging support for script editors. | ||
*/ | ||
const scriptEditorDebuggerExtension: JupyterFrontEndPlugin<void> = { | ||
id: 'elyra-script-debugger', | ||
autoStart: true, | ||
requires: [IDebugger, IEditorTracker], | ||
optional: [ILabShell], | ||
activate: ( | ||
app: JupyterFrontEnd, | ||
debug: IDebugger, | ||
editorTracker: IEditorTracker, | ||
labShell: ILabShell | null | ||
) => { | ||
console.log('Elyra - script-debugger extension is activated!'); | ||
|
||
const handler = new Debugger.Handler({ | ||
type: 'file', | ||
shell: app.shell, | ||
service: debug | ||
}); | ||
|
||
const activeSessions: { [id: string]: Session.ISessionConnection } = {}; | ||
const kernelManager = new KernelManager(); | ||
const sessionManager = new SessionManager({ | ||
kernelManager: kernelManager | ||
}); | ||
|
||
const updateDebugger = async (widget: ScriptEditor): Promise<void> => { | ||
const widgetInFocus = app.shell.currentWidget; | ||
if (widget !== widgetInFocus) { | ||
return; | ||
} | ||
|
||
const kernelSelection = (widget as ScriptEditor).kernelSelection; | ||
const debuggerAvailable = await widget.debuggerAvailable(kernelSelection); | ||
|
||
if (!debuggerAvailable) { | ||
return; | ||
} | ||
|
||
const sessions = app.serviceManager.sessions; | ||
try { | ||
const path = widget.context.path; | ||
let sessionModel = await sessions.findByPath(path); | ||
if (!sessionModel) { | ||
// Start a kernel session for the selected kernel supporting debug | ||
const sessionConnection = await startSession(kernelSelection, path); | ||
sessionModel = await sessions.findByPath(path); | ||
if (sessionConnection && sessionModel) { | ||
activeSessions[sessionModel.id] = sessionConnection; | ||
} | ||
} | ||
|
||
if (sessionModel) { | ||
let sessionConnection: Session.ISessionConnection | null = | ||
activeSessions[sessionModel.id]; | ||
if (!sessionConnection) { | ||
// Use `connectTo` only if the session does not exist. | ||
// `connectTo` sends a kernel_info_request on the shell | ||
// channel, which blocks the debug session restore when waiting | ||
// for the kernel to be ready | ||
sessionConnection = sessions.connectTo({ model: sessionModel }); | ||
activeSessions[sessionModel.id] = sessionConnection; | ||
} | ||
if (sessionModel.kernel?.name !== kernelSelection) { | ||
// New kernel selection detected, restart session connection for new kernel | ||
await shutdownSession(sessionConnection); | ||
|
||
sessionConnection = await startSession(kernelSelection, path); | ||
sessionModel = await sessions.findByPath(path); | ||
if (sessionConnection && sessionModel) { | ||
activeSessions[sessionModel.id] = sessionConnection; | ||
} | ||
} | ||
|
||
// Temporary solution to give enough time for the handler to update the UI on page reload. | ||
setTimeout(async () => { | ||
await handler.update(widget, sessionConnection); | ||
app.commands.notifyCommandChanged(); | ||
}, 1000); | ||
} | ||
} catch (error) { | ||
console.warn( | ||
'Exception: session connection = ' + JSON.stringify(error) | ||
); | ||
} | ||
}; | ||
|
||
// Use a weakmap to track the callback function used by signal listeners | ||
// The object is cleared by garbabe collector when no longer in use avoiding memory leaks | ||
// Key: ScriptEditor widget | ||
// Value: instance of updateDebugger function | ||
const callbackControl = new WeakMap<ScriptEditor, () => Promise<void>>(); | ||
|
||
const update = async (widget: Widget | null): Promise<void> => { | ||
if (widget instanceof ScriptEditor) { | ||
let callbackFn = callbackControl.get(widget); | ||
if (!callbackFn) { | ||
callbackFn = (): Promise<void> => updateDebugger(widget); | ||
callbackControl.set(widget, callbackFn); | ||
} | ||
updateDebugger(widget); | ||
|
||
// Listen to possible kernel selection changes | ||
widget.kernelSelectionChanged.disconnect(callbackFn); | ||
widget.kernelSelectionChanged.connect(callbackFn); | ||
} | ||
}; | ||
|
||
if (labShell) { | ||
// Listen to main area's current focus changes. | ||
labShell.currentChanged.connect((_, widget) => { | ||
return update(widget.newValue); | ||
}); | ||
} | ||
|
||
if (editorTracker) { | ||
// Listen to script editor's current instance changes. | ||
editorTracker.currentChanged.connect((_, widget) => { | ||
return update(widget); | ||
}); | ||
} | ||
|
||
const startSession = async ( | ||
kernelSelection: string, | ||
path: string | ||
): Promise<Session.ISessionConnection | null> => { | ||
const options: Session.ISessionOptions = { | ||
kernel: { | ||
name: kernelSelection | ||
}, | ||
path: path, | ||
type: 'file', | ||
name: path | ||
}; | ||
let sessionConnection; | ||
try { | ||
sessionConnection = await sessionManager.startNew(options); | ||
sessionConnection.setPath(path); | ||
console.log( | ||
`Kernel session started for ${path} with selected ${kernelSelection} kernel.` | ||
); | ||
} catch (error) { | ||
console.warn('Exception: start session = ' + JSON.stringify(error)); | ||
sessionConnection = null; | ||
} | ||
return sessionConnection; | ||
}; | ||
|
||
const shutdownSession = async ( | ||
sessionConnection: Session.ISessionConnection | ||
): Promise<void> => { | ||
try { | ||
const kernelName = sessionConnection.kernel?.name; | ||
await sessionConnection.shutdown(); | ||
console.log(`${kernelName} kernel shut down.`); | ||
} catch (error) { | ||
console.warn('Exception: shutdown = ' + JSON.stringify(error)); | ||
} | ||
}; | ||
} | ||
}; | ||
|
||
export default scriptEditorDebuggerExtension; |
Oops, something went wrong.