Python code completion functionality using Jedi for development environments. Provides intelligent code suggestions, parameter hints, and documentation for Python code running in the WebAssembly worker.
Get code completion suggestions for Python code at a specific cursor position.
/**
* Get code completions for Python code
* @param request - Code completion request with code, line, and column
* @returns Promise that resolves with completion suggestions
*/
getCodeCompletions(request: CodeCompletionRequest): Promise<CodeCompletionResponse>;
/**
* Code completion request structure
*/
interface CodeCompletionRequest {
/** Python code to analyze */
code: string;
/** Line number (0-based) where completion is requested */
line: number;
/** Column number (0-based) where completion is requested */
column: number;
}
/**
* Individual code completion suggestion
*/
interface CodeCompletion {
/** Display label for the completion */
label: string;
/** Type of completion (function, class, variable, etc.) */
type: string;
/** Documentation string for the completion */
docstring: string;
/** Number of characters to replace from cursor position */
completion_prefix_length: number;
}
/**
* Array of completion suggestions
*/
type CodeCompletionResponse = CodeCompletion[];Usage Examples:
import { WorkerProxy } from "@gradio/wasm";
const worker = new WorkerProxy({
gradioWheelUrl: "https://example.com/gradio.whl",
gradioClientWheelUrl: "https://example.com/gradio_client.whl",
files: {},
requirements: ["numpy", "pandas", "matplotlib"],
sharedWorkerMode: false
});
worker.addEventListener("initialization-completed", async () => {
// Basic code completion
const completions = await worker.getCodeCompletions({
code: "import numpy as np\nnp.",
line: 1,
column: 3
});
console.log("NumPy completions:");
completions.forEach(completion => {
console.log(` ${completion.label} (${completion.type})`);
if (completion.docstring) {
console.log(` ${completion.docstring.split('\n')[0]}`);
}
});
// Method completion
const methodCompletions = await worker.getCodeCompletions({
code: `
import pandas as pd
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
df.`,
line: 2,
column: 3
});
console.log("DataFrame method completions:");
methodCompletions.slice(0, 10).forEach(completion => {
console.log(` ${completion.label} (${completion.type})`);
});
// Variable completion in context
const contextCompletions = await worker.getCodeCompletions({
code: `
def process_data(data_frame, column_name):
result = data_frame[column_name].mean()
data_`,
line: 2,
column: 9
});
console.log("Context-aware completions:");
contextCompletions.forEach(completion => {
console.log(` ${completion.label} (${completion.type})`);
});
});Example integration with a code editor for real-time completions:
import { WorkerProxy } from "@gradio/wasm";
class PythonCodeEditor {
private worker: WorkerProxy;
private editorElement: HTMLTextAreaElement;
private completionsList: HTMLDivElement;
private currentCompletions: CodeCompletion[] = [];
constructor(editorElement: HTMLTextAreaElement, worker: WorkerProxy) {
this.worker = worker;
this.editorElement = editorElement;
this.setupEditor();
this.createCompletionsUI();
}
private setupEditor() {
// Set up event listeners for code completion
this.editorElement.addEventListener('input', () => {
this.debounceCompletion();
});
this.editorElement.addEventListener('keydown', (event) => {
this.handleKeyDown(event);
});
// Position cursor tracking
this.editorElement.addEventListener('click', () => {
this.hideCompletions();
});
}
private createCompletionsUI() {
this.completionsList = document.createElement('div');
this.completionsList.className = 'completions-list';
this.completionsList.style.cssText = `
position: absolute;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
max-height: 200px;
overflow-y: auto;
z-index: 1000;
display: none;
`;
document.body.appendChild(this.completionsList);
}
private debounceCompletion = this.debounce(async () => {
await this.triggerCompletion();
}, 300);
private debounce(func: Function, wait: number) {
let timeout: NodeJS.Timeout;
return function executedFunction(...args: any[]) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
private async triggerCompletion() {
const cursorPosition = this.getCursorPosition();
const code = this.editorElement.value;
try {
const completions = await this.worker.getCodeCompletions({
code: code,
line: cursorPosition.line,
column: cursorPosition.column
});
if (completions.length > 0) {
this.showCompletions(completions, cursorPosition);
} else {
this.hideCompletions();
}
} catch (error) {
console.error("Code completion error:", error);
this.hideCompletions();
}
}
private getCursorPosition(): { line: number; column: number } {
const textBeforeCursor = this.editorElement.value.substring(0, this.editorElement.selectionStart);
const lines = textBeforeCursor.split('\n');
return {
line: lines.length - 1,
column: lines[lines.length - 1].length
};
}
private showCompletions(completions: CodeCompletion[], cursorPosition: { line: number; column: number }) {
this.currentCompletions = completions;
// Clear previous completions
this.completionsList.innerHTML = '';
// Add completion items
completions.slice(0, 10).forEach((completion, index) => {
const item = document.createElement('div');
item.className = 'completion-item';
item.style.cssText = `
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #eee;
font-family: monospace;
`;
const typeColor = this.getCompletionTypeColor(completion.type);
item.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: bold;">${completion.label}</span>
<span style="font-size: 0.8em; color: ${typeColor};">${completion.type}</span>
</div>
${completion.docstring ? `<div style="font-size: 0.8em; color: #666; margin-top: 4px;">${completion.docstring.split('\n')[0]}</div>` : ''}
`;
item.addEventListener('click', () => {
this.insertCompletion(completion);
});
item.addEventListener('mouseenter', () => {
item.style.backgroundColor = '#f0f0f0';
});
item.addEventListener('mouseleave', () => {
item.style.backgroundColor = 'white';
});
this.completionsList.appendChild(item);
});
// Position the completions list
this.positionCompletionsList();
this.completionsList.style.display = 'block';
}
private getCompletionTypeColor(type: string): string {
const colors = {
'function': '#0066cc',
'method': '#0066cc',
'class': '#cc6600',
'variable': '#009900',
'module': '#990099',
'keyword': '#cc0000'
};
return colors[type] || '#666666';
}
private positionCompletionsList() {
const editorRect = this.editorElement.getBoundingClientRect();
const cursorPosition = this.getCursorPosition();
// Approximate cursor position (this would need more sophisticated calculation in practice)
const lineHeight = 20;
const charWidth = 8;
this.completionsList.style.left = `${editorRect.left + cursorPosition.column * charWidth}px`;
this.completionsList.style.top = `${editorRect.top + (cursorPosition.line + 1) * lineHeight}px`;
}
private insertCompletion(completion: CodeCompletion) {
const cursorPos = this.editorElement.selectionStart;
const textBefore = this.editorElement.value.substring(0, cursorPos - completion.completion_prefix_length);
const textAfter = this.editorElement.value.substring(cursorPos);
this.editorElement.value = textBefore + completion.label + textAfter;
this.editorElement.selectionStart = this.editorElement.selectionEnd = textBefore.length + completion.label.length;
this.hideCompletions();
this.editorElement.focus();
}
private handleKeyDown(event: KeyboardEvent) {
if (this.completionsList.style.display === 'block') {
switch (event.key) {
case 'Escape':
event.preventDefault();
this.hideCompletions();
break;
case 'Tab':
case 'Enter':
event.preventDefault();
if (this.currentCompletions.length > 0) {
this.insertCompletion(this.currentCompletions[0]);
}
break;
}
}
}
private hideCompletions() {
this.completionsList.style.display = 'none';
this.currentCompletions = [];
}
}
// Usage
const editorElement = document.getElementById('python-editor') as HTMLTextAreaElement;
const codeEditor = new PythonCodeEditor(editorElement, worker);Enhanced completion features for complex scenarios:
import { WorkerProxy } from "@gradio/wasm";
class AdvancedCodeCompletion {
private worker: WorkerProxy;
private completionCache: Map<string, { completions: CodeCompletion[]; timestamp: number }> = new Map();
private cacheTimeout = 60000; // 1 minute cache
constructor(worker: WorkerProxy) {
this.worker = worker;
}
async getContextualCompletions(
code: string,
line: number,
column: number,
options: {
includePrivate?: boolean;
includeBuiltins?: boolean;
maxCompletions?: number;
prioritizeByType?: string[];
} = {}
): Promise<CodeCompletion[]> {
const cacheKey = `${code.substring(0, 1000)}:${line}:${column}`;
const cached = this.completionCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
console.log("Using cached completions");
return this.filterAndSortCompletions(cached.completions, options);
}
try {
const completions = await this.worker.getCodeCompletions({
code,
line,
column
});
// Cache the results
this.completionCache.set(cacheKey, {
completions,
timestamp: Date.now()
});
return this.filterAndSortCompletions(completions, options);
} catch (error) {
console.error("Code completion failed:", error);
return [];
}
}
private filterAndSortCompletions(
completions: CodeCompletion[],
options: {
includePrivate?: boolean;
includeBuiltins?: boolean;
maxCompletions?: number;
prioritizeByType?: string[];
}
): CodeCompletion[] {
let filtered = completions;
// Filter private members
if (!options.includePrivate) {
filtered = filtered.filter(comp => !comp.label.startsWith('_'));
}
// Filter builtins
if (!options.includeBuiltins) {
filtered = filtered.filter(comp => comp.type !== 'builtin');
}
// Sort by priority types
if (options.prioritizeByType && options.prioritizeByType.length > 0) {
filtered.sort((a, b) => {
const aPriority = options.prioritizeByType!.indexOf(a.type);
const bPriority = options.prioritizeByType!.indexOf(b.type);
if (aPriority !== -1 && bPriority !== -1) {
return aPriority - bPriority;
} else if (aPriority !== -1) {
return -1;
} else if (bPriority !== -1) {
return 1;
} else {
return a.label.localeCompare(b.label);
}
});
} else {
// Default sorting by label
filtered.sort((a, b) => a.label.localeCompare(b.label));
}
// Limit results
if (options.maxCompletions && options.maxCompletions > 0) {
filtered = filtered.slice(0, options.maxCompletions);
}
return filtered;
}
async getSignatureHelp(code: string, line: number, column: number): Promise<{
signatures: Array<{
label: string;
documentation: string;
parameters: Array<{
label: string;
documentation: string;
}>;
}>;
activeSignature: number;
activeParameter: number;
} | null> {
// This would require additional implementation in the worker
// For now, we'll simulate signature help using completions
try {
await this.worker.runPythonCode(`
# Enhanced code analysis for signature help
import ast
import inspect
import sys
code_to_analyze = '''${code.replace(/'/g, "\\'")}'''
target_line = ${line}
target_column = ${column}
# This would need sophisticated AST analysis
# For now, we'll return a placeholder
print("SIGNATURE_HELP: Not implemented")
`);
return null; // Placeholder
} catch (error) {
console.error("Signature help failed:", error);
return null;
}
}
async getHoverInformation(code: string, line: number, column: number): Promise<{
content: string;
range: { start: { line: number; column: number }; end: { line: number; column: number } };
} | null> {
try {
// Get completions at the current position
const completions = await this.worker.getCodeCompletions({
code,
line,
column
});
// Find the most relevant completion
const currentWord = this.getCurrentWord(code, line, column);
const matchingCompletion = completions.find(comp =>
comp.label === currentWord || comp.label.startsWith(currentWord)
);
if (matchingCompletion && matchingCompletion.docstring) {
return {
content: `**${matchingCompletion.label}** (${matchingCompletion.type})\n\n${matchingCompletion.docstring}`,
range: {
start: { line, column: column - currentWord.length },
end: { line, column }
}
};
}
return null;
} catch (error) {
console.error("Hover information failed:", error);
return null;
}
}
private getCurrentWord(code: string, line: number, column: number): string {
const lines = code.split('\n');
if (line >= lines.length) return '';
const currentLine = lines[line];
const beforeCursor = currentLine.substring(0, column);
const afterCursor = currentLine.substring(column);
// Find word boundaries
const wordBefore = beforeCursor.match(/[a-zA-Z_][a-zA-Z0-9_]*$/);
const wordAfter = afterCursor.match(/^[a-zA-Z0-9_]*/);
return (wordBefore ? wordBefore[0] : '') + (wordAfter ? wordAfter[0] : '');
}
clearCache(): void {
this.completionCache.clear();
}
getCacheStats(): { size: number; entries: string[] } {
return {
size: this.completionCache.size,
entries: Array.from(this.completionCache.keys())
};
}
}
// Usage
const advancedCompletion = new AdvancedCodeCompletion(worker);
// Get filtered completions
const completions = await advancedCompletion.getContextualCompletions(
"import pandas as pd\ndf = pd.DataFrame()\ndf.",
2,
3,
{
includePrivate: false,
maxCompletions: 20,
prioritizeByType: ['method', 'function', 'property']
}
);
// Get hover information
const hoverInfo = await advancedCompletion.getHoverInformation(
"import numpy as np\nnp.array",
1,
8
);
if (hoverInfo) {
console.log("Hover info:", hoverInfo.content);
}