⚡️ No JavaScript required for extension logic — write VS Code extensions in pure Python.
Warning
This project is freezed/archived due i have no time. Maybe will continue work in summer.
⚠️ Note: Preview was recorded when the project was called pyvscode. Now project renamed to pyxend.
- 🧠 Simple Python API for defining commands
- ⚙️ CLI tool to scaffold, sync, build, and publish extensions
- 🧩 Template-based generation of
extension.jsandpackage.json - 🔁 Context-aware Python execution with editor data (selected text, cursor, file)
- 📦 Easy packaging using
vsce
pip install pyxendOr using git repository:
git clone https://github.com/codeflane/pyxend
cd pyxend
pip install -e .Make sure Node.js and vsce are installed:
npm install -g vsce
pyxend init "My Extension Name" myextensionEdit main.py:
from pyxend import Extension, ModalType
ext = Extension()
@ext.command('hello')
def say_hello(ctx):
ext.show_modal("Hello from Python!", type=ModalType.INFO)
ext.run()pyxend syncpyxend build
code --install-extension your-extension.vsixAll CLI commands accept a --target (or -t) option to specify the working directory (defaults to current folder).
pyxend init "Display Name" extension_nameInit new project.
- Display Name: extension display name (that showing in extension hub)
- Extension Name: extension name (defaults to display name)
main.py(logic)extension.js(bridge)package.json(extension metadata).vscodeignore
pyxend syncSync Python decorators in main.py with extension.js and package.json
pyxend metadata -v 0.0.1 -e 1.70.0 -d desc -t title -n name -g gitUpdate package.json metadata
| Option | Description |
|---|---|
--engine / -e |
VS Code engine version |
--description / -d |
Description of your extension |
--git / -g |
GitHub repo URL |
--name / -n |
Display name |
--version / -v |
Extension version |
pyxend license authorCreate LICENSE file (now only MIT support). License is required for creating extensions
The core API is exposed via the Extension class.
Decorator to register a command that can be invoked from VS Code.
name- The command name (e.g.,"sayHello").title- Title to display in the Command Palette. Defaults toname
When the command is invoked, it receives a context dictionary with useful metadata:
{
"selected_text": "Hello", // Currently selected text
"language": "python", // Opened file language
"cursor_pos": {"line": 3, "character": 15}, // Current cursor position
"file_path": "D:/projects/example.py", // Opened file path
"all_text": "Hello World", // File content
"cursor_word": "Hello", // the word under the cursor
"lines": 3, // Lines count in file
"file_size": 12, // File size in bytes
"opened_files": ["D:/projects/example.py", "D:/test.txt"], // Currently opened files
"is_saved": false, //Is file saved
"workspace": "D:/projects" //Opened workspace folder
}@ext.command("sayHello", title="Say Hello")
def say_hello(context):
ext.show_modal(f"Hi! You selected: {context['selected_text']}")Decorator to register an event that can be invoked from VS Code.
event- Event type (Eventenum).
Context same as Command decorator. See pyxend -> Extension API -> Command decorator -> Context
@ext.command("sayHello", title="Say Hello")
def say_hello(context):
ext.show_modal(f"Hi! You selected: {context['selected_text']}")Show modal popup
- message - The message to display.
- type - Must be one of the ModalType values:
- ModalType.INFO - modal with an blue informational (i) icon (default)
- ModalType.WARNING - modal with an yellow warning /!\ icon
- ModalType.ERROR - modal with an red error (x) icon
ext.show_modal("This is an error", type=ModalType.ERROR) #Show error modal with text "This is an error"Replace the currently selected text in the editor. (deprecated)
text- The text that will replace the current selection.
ext.replace_selected_text("Replaced content.") #Replace currently selected text to "Replace content."Insert new text.
-
text- The text to insert. -
preset- Position preset. Must be one of the InsertTextPreset values:- InsertTextPreset.START - Insert text at the start of file
- InsertTextPreset.CURSOR - Insert text after cursor position
- InsertTextPreset.CUSTOM - Insert text at custom position (use
lineandcharacterto provide it) (default) - InsertTextPreset.END - Insert text at the end of file
-
line- Line number to insert text. Requires only when preset isCUSTOM -
character- Character number to insert text. Requires only when preset isCUSTOM
ext.insert_text("Inserted text.", preset=InsertTextPreset.CURSOR) #Insert text "Inserted text." after cursor positionReplace text.
-
text- The text to replace. -
preset- Position preset. Must be one of the ReplaceTextPreset values:- ReplaceTextPreset.SELECTED - Replace selected text
- ReplaceTextPreset.CUSTOM - Replace text at custom position (use
start_line,start_character,end_lineandend_characterto provide it) (default) - ReplaceTextPreset.ALL - Replace whole file
-
start_line- Start line number to replace text. Requires only when preset isCUSTOM -
start_character- Start character number to replace text. Requires only when preset isCUSTOM -
end_line- End line number to replace text. Requires only when preset isCUSTOM -
end_character- End character number to replace text. Requires only when preset isCUSTOM
ext.replace_text("Replaceed text.", preset=InsertTextPreset.ALL) #Replace all file text to "Replaced text."Open a file in the editor by its path.
path- Full path to the file.
ext.open_file("D:/projects/example.py") #open "D:/projects/example.py" in editorMove the editor’s cursor to the specified position.
line- Line number.character- Character number.
ext.set_cursor_pos(5, 10) #move cursor to line 5, character 10Save the current file.
ext.save_file() #save current fileReplace the entire content of the file. (deprecated)
text- The new content for the whole file.
ext.replace_all_text("print('Hello, World!')\n") #replace all file text to "print('Hello, World!')"Execute a command in a new or existing terminal.
command- The terminal command to execute.name(optional) - Name of the terminal instance. Default is "pyxend terminal"
ext.run_terminal_command("echo 'Hello World'") #create new terminal and echo "Hello World"Delete currently selected text
ext.delete_selected_text() #delete selected textDelete currently opened file (Do not recommend to use)
ext.delete_file() #delete fileShow status message in a bottom of screen.
message- The message to show.timeout(optional) - How long message will show in ms. Defaults to 3000ms (3s)
ext.status_message("Hello from pyxend", 1000) #show status message "Hello from pyxend" for 1 second- All actions (
ext.show_modal,ext.insert_text, etc.) are collected into a list during Python execution and returned as a single JSON batch to VS Code. - This means VS Code does not execute Python actions one-by-one. It only runs Python once, receives all the actions at once, and then executes them sequentially in JavaScript.
- Any delay (
time.sleep) or conditional logic in Python will happen before any action is performed in the editor.
ext.show_modal("First")
time.sleep(1)
ext.show_modal("Second")This will result in both modals appearing immediately, one after the other — not with a delay between them.
We are planning to add streaming or asynchronous action execution in a future version (v1.0 or v2.0) so that:
actions like ext.show_modal() can be executed live, one at a time, and delays or dynamic logic will reflect in real-time in the editor.
We already have some ideas on how to implement this — stay tuned!
pyxend bridges VS Code and Python.
When a command is executed:
-
VS Code (JavaScript)
- Collects the file context (selected text, full code, cursor, file path, etc.)
- Launches the Python script (
main.py) and passes the command name and context as JSON.
-
Python (pyxend)
- Receives the command and context.
- Executes the matching
@ext.command(...)function. - Generates actions list from executed commands.
- Return actions list to the JS.
-
VS Code (JavaScript)
- Parses the returned actions.
- Executes them one by one using the VS Code API (editing text, opening files, setting cursor position, etc.)
This allows Python to control editor behavior dynamically.
To add a new custom action that isn’t supported yet:
Append an action manually:
ext.actions.append({
"action": "highlight_range",
"line_start": 5,
"line_end": 8
})Open extension.js and add a new case to handle it:
case "highlight_range":
if (!editor) return;
const start = new vscode.Position(action.line_start, 0);
const end = new vscode.Position(action.line_end, 0);
const decorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: "rgba(255,255,0,0.2)"
});
editor.setDecorations(decorationType, [new vscode.Range(start, end)]);
break;This allows pyxend to be fully extensible.
Note: after syncing (
pyxend sync), all JS will overwrite. It will be fixed in future versions.
See full change log in CHANGELOG.md
Added events and 2 new actions
Added 2 new actions and reworked replace/insert text methods
Added 3 new values in context, renamed manifest → metadata
Fixed packaging bug, improved error modals, typo fixes
Thank you for checking out pyxend! If you find it useful, please consider:
- ⭐ Starring the GitHub repository — it really helps the project grow.
- 🛠 Opening an issue or PR with your suggestions or improvements.
- 📢 Sharing the project with fellow Python developers who want to build VS Code extensions without touching JavaScript.
Let’s make Python-powered VS Code extensions mainstream!
