Skip to content

Commit

Permalink
Merge pull request #842 from TypeFox/issue-690-debugger
Browse files Browse the repository at this point in the history
Integrate python debugger
  • Loading branch information
kaisalmen authored Feb 10, 2025
2 parents ec7cd1f + 400f81c commit 9b3e699
Show file tree
Hide file tree
Showing 19 changed files with 1,087 additions and 110 deletions.
63 changes: 63 additions & 0 deletions .github/workflows/debugger-py.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Python Debugger Image

on:
workflow_dispatch:
push:
branches:
- issue-690-debugger

env:
REGISTRY: ghcr.io
REPO_NAME: ${{ github.repository }}
PATH_CONTEXT: ./packages/examples/resources/debugger
CONTAINER_NAME: debugger-py

jobs:
image-debugger-py:
name: Build & Deploy Python Debugger
runs-on: ubuntu-latest

permissions:
contents: read
packages: write
attestations: write
id-token: write

timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.REPO_NAME }}/${{ env.CONTAINER_NAME }}
# enforce latest tag for now
tags: |
type=raw,value=latest
- name: Build & Push
id: push
uses: docker/build-push-action@v6
with:
context: .
file: ${{ env.PATH_CONTEXT }}/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

- name: Attest
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ env.REPO_NAME }}/${{ env.CONTAINER_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
5 changes: 4 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ <h3>Langium</h3>
<br>

<h3>Python</h3>
Please execute <b><code>npm run start:example:server:python</code></b> beforehand:<br>
Please execute <b><code>npm run start:example:server:python</code></b> beforehand.<br>
Debugger requires docker. Please execute <b><code>docker compose -f ./packages/examples/resources/debugger/docker-compose.yml up -d</code></b> beforehand.<br>
<a href="./packages/examples/python.html">Python Language Client & Pyright Language Server (Web Socket)</a><br>

<h3>Java / Eclipse JDS LS</h3>
Expand Down Expand Up @@ -86,6 +87,8 @@ <h3>TypeScript</h3>
<br>

<h2>Monaco Editor React</h2>
Please execute <b><code>npm run start:example:server:python</code></b> beforehand.<br>
Debugger requires docker. Please execute <b><code>docker compose -f ./packages/examples/resources/debugger/docker-compose.yml up -d</code></b> beforehand.<br>
<a href="./packages/examples/react_appPlayground.html">React: Application Playground</a>
<br>
<a href="./packages/examples/react_statemachine.html">React: Langium Statemachine Language Client & Language Server (Worker)</a>
Expand Down
362 changes: 362 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"dependencies": {
"@codingame/monaco-vscode-configuration-service-override": "~13.1.6",
"@codingame/monaco-vscode-cpp-default-extension": "~13.1.6",
"@codingame/monaco-vscode-debug-service-override": "~13.1.6",
"@codingame/monaco-vscode-editor-api": "~13.1.6",
"@codingame/monaco-vscode-environment-service-override": "~13.1.6",
"@codingame/monaco-vscode-explorer-service-override": "~13.1.6",
Expand All @@ -84,6 +85,7 @@
"@codingame/monaco-vscode-keybindings-service-override": "~13.1.6",
"@codingame/monaco-vscode-lifecycle-service-override": "~13.1.6",
"@codingame/monaco-vscode-localization-service-override": "~13.1.6",
"@codingame/monaco-vscode-preferences-service-override": "~13.1.6",
"@codingame/monaco-vscode-python-default-extension": "~13.1.6",
"@codingame/monaco-vscode-remote-agent-service-override": "~13.1.6",
"@codingame/monaco-vscode-search-result-default-extension": "~13.1.6",
Expand All @@ -92,6 +94,7 @@
"@codingame/monaco-vscode-standalone-json-language-features": "~13.1.6",
"@codingame/monaco-vscode-standalone-languages": "~13.1.6",
"@codingame/monaco-vscode-standalone-typescript-language-features": "~13.1.6",
"@codingame/monaco-vscode-testing-service-override": "~13.1.6",
"@codingame/monaco-vscode-storage-service-override": "~13.1.6",
"@codingame/monaco-vscode-textmate-service-override": "~13.1.6",
"@codingame/monaco-vscode-theme-defaults-default-extension": "~13.1.6",
Expand Down
13 changes: 5 additions & 8 deletions packages/examples/python.html
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
<!DOCTYPE html>
<html>
<html lang="en">

<head>
<title>Python Language Client & Pyright Language Server (Web Socket)</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="./resources/styles/views.css">
</head>

<body>
<div class="exampleHeadelineDiv">
<b class="exampleHeadeline">Python Language Client & Pyright Language Server (Web Socket)</b> - [<a href="../../index.html">Back to Index</a>]
<br>
<button type="button" id="button-start">Start</button>
<button type="button" id="button-dispose">Dispose</button>
</div>
<div id="monaco-editor-root" style="width:800px;height:600px;border:1px solid grey"></div>
<script type="module">
<script type="module" rel="modulepreload">
import { runPythonWrapper } from "./src/python/client/main.ts";

runPythonWrapper();
Expand Down
5 changes: 1 addition & 4 deletions packages/examples/react_python.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React: Python Language Client & Language Server (Web Socket)</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="./resources/styles/views.css">
</head>

<body>
<div class="exampleHeadelineDiv">
<b class="exampleHeadeline">React: Python Language Client & Language Server (Web Socket)</b> - [<a href="../../index.html">Back to Index</a>]
<br>
<button type="button" id="button-start">Start</button>
<label>Enable Strict mode:</label><input type="checkbox" id="checkbox-strictmode" />
<button type="button" id="button-dispose">Dispose</button>
</div>
<div id="react-root"></div>
<script type="module">
Expand Down
17 changes: 17 additions & 0 deletions packages/examples/resources/debugger/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM ghcr.io/graalvm/graalpy-community:24

RUN curl https://get.volta.sh | bash
ENV VOLTA_FEATURE_PNPM=1
ENV VOLTA_HOME "/root/.volta"
ENV PATH "$VOLTA_HOME/bin:$PATH"

RUN volta install node@22

RUN mkdir -p /home/mlc/workspace
RUN mkdir -p /home/mlc/server/src

COPY ./packages/examples/resources/debugger/package.json /home/mlc/server
COPY ./packages/examples/src/debugger/server/debugServer.ts /home/mlc/server/src
COPY ./packages/examples/src/debugger/server/DAPSocket.ts /home/mlc/server/src

WORKDIR /home/mlc/server
19 changes: 19 additions & 0 deletions packages/examples/resources/debugger/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
services:
debugger:
container_name: debugger-py
image: ghcr.io/typefox/monaco-languageclient/debugger-py:latest
build:
dockerfile: ./packages/examples/resources/debugger/Dockerfile
context: ../../../..
# only linux/amd64 for now
platforms:
- "linux/amd64"
platform: linux/amd64
ports:
- 55555:5555
tty: true
# just for completness. Is already set in the Dockerfile
working_dir: /home/mlc/server
command: [
"bash", "-c", "npm run start"
]
44 changes: 44 additions & 0 deletions packages/examples/resources/debugger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "graalpy-debugger",
"version": "2025.2.1",
"description": "Monaco Language client Graalpy Debugger",
"author": {
"name": "TypeFox GmbH",
"url": "http://www.typefox.io"
},
"license": "MIT",
"type": "module",
"main": "./dist/debugServer.js",
"module": "./dist/debugServer.js",
"exports": {
".": {
"types": "./dist/debugServer.d.ts",
"default": "./dist/debugServer.js"
}
},
"typesVersions": {
"*": {
".": [
"dist/debugServer"
]
}
},
"engines": {
"node": ">=22.13.1",
"npm": ">=10.9.2"
},
"volta": {
"node": "22.13.1",
"npm": "10.9.2"
},
"dependencies": {
"express": "~4.21.2",
"ws": "~8.18.0"
},
"devDependencies": {
"tsx": "~4.19.2"
},
"scripts": {
"start": "npm i && tsx src/debugServer.ts"
}
}
4 changes: 4 additions & 0 deletions packages/examples/resources/python/hello2.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
def print_hello():

x=5
print("Hello World!")

print_hello()
114 changes: 114 additions & 0 deletions packages/examples/src/debugger/client/debugger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) 2024 TypeFox and others.
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */

import * as vscode from 'vscode';
import type { ExtensionConfig } from 'monaco-editor-wrapper';
import type { ConfigParams, InitMessage } from '../common/definitions.js';

// This is derived from:
// https://github.com/CodinGame/monaco-vscode-api/blob/main/demo/src/features/debugger.ts
// The client configuration is generic and can be used for a another language

export const provideDebuggerExtensionConfig = (config: ConfigParams): ExtensionConfig => {
const filesOrContents = new Map<string, string | URL>();
filesOrContents.set('./extension.js', '// nothing');

return {
config: {
name: config.extensionName,
publisher: 'TypeFox',
version: '1.0.0',
engines: {
vscode: '*'
},
// A browser field is mandatory for the extension to be flagged as `web`
browser: 'extension.js',
contributes: {
debuggers: [
{
type: config.languageId,
label: 'Test',
languages: [config.languageId]
}
],
breakpoints: [
{
language: config.languageId
}
]
},
activationEvents: [
'onDebug'
]
},
filesOrContents
};
};

export const confiugureDebugging = async (api: typeof vscode, config: ConfigParams) => {
class WebsocketDebugAdapter implements vscode.DebugAdapter {
private websocket: WebSocket;

constructor(websocket: WebSocket) {
this.websocket = websocket;
this.websocket.onmessage = (message) => {
this._onDidSendMessage.fire(JSON.parse(message.data));
};
}

_onDidSendMessage = new api.EventEmitter<vscode.DebugProtocolMessage>();
onDidSendMessage = this._onDidSendMessage.event;

handleMessage(message: vscode.DebugProtocolMessage): void {
// path with on Windows (Chrome/Firefox) arrive here with \\ and not like expected with /
// Chrome on Ubuntu behaves as expected
const msg = JSON.stringify(message).replaceAll('\\\\', '/');
this.websocket.send(msg);
}

dispose() {
this.websocket.close();
}
}

api.debug.registerDebugAdapterDescriptorFactory(config.languageId, {
async createDebugAdapterDescriptor() {
const websocket = new WebSocket(`${config.protocol}://${config.hostname}:${config.port}`);

await new Promise((resolve, reject) => {
websocket.onopen = resolve;
websocket.onerror = () =>
reject(new Error(`Unable to connect to debugger server. Run "${config.helpContainerCmd}"`));
});

const adapter = new WebsocketDebugAdapter(websocket);

const initMessage: InitMessage = {
id: 'init',
files: {},
// the default file is the one that will be used by the debugger
defaultFile: config.defaultFile,
debuggerExecCall: config.debuggerExecCall
};
for (const [name, fileDef] of config.files.entries()) {
console.log(`Found: ${name} Sending file: ${fileDef.path}`);
initMessage.files[name] = {
path: fileDef.path,
code: fileDef.code,
uri: fileDef.uri
};
}
websocket.send(JSON.stringify(initMessage));

// eslint-disable-next-line @typescript-eslint/no-explicit-any
adapter.onDidSendMessage((message: any) => {
if (message.type === 'event' && message.event === 'output') {
console.log('OUTPUT', message.body.output);
}
});
return new api.DebugAdapterInlineImplementation(adapter);
}
});
};
Loading

0 comments on commit 9b3e699

Please sign in to comment.