p4-vscode_designer_extension/src/generator/CodeGenerator.ts
2025-12-23 05:43:33 +03:00

178 lines
5.8 KiB
TypeScript

import { DesignData, WidgetData } from './types';
import {
getVariableName,
generateVariableNames,
getWidgetTypeForGeneration,
indentText,
} from './utils';
import {
getWidgetParameters,
getPlaceParameters,
generateWidgetContent,
} from './widgetHelpers';
import { generateEventHandlers, getWidgetEventBindings } from './eventHelpers';
export class CodeGenerator {
private indentLevel = 0;
private designData: DesignData | null = null;
public generateTkinterCode(designData: DesignData): string {
this.designData = designData;
const className = designData.form.className || 'Application';
console.log(
'[Generator] Start, widgets:',
designData.widgets.length,
'events:',
designData.events?.length || 0
);
const lines: string[] = [];
const nameMap = generateVariableNames(designData.widgets);
lines.push('import tkinter as tk');
lines.push('from tkinter import ttk');
lines.push('');
lines.push(`class ${className}:`);
this.indentLevel = 1;
lines.push(this.indent('def __init__(self):'));
this.indentLevel = 2;
lines.push(this.indent('self.root = tk.Tk()'));
lines.push(this.indent(`self.root.title("${designData.form.title}")`));
lines.push(
this.indent(
`self.root.geometry("${designData.form.size.width}x${designData.form.size.height}")`
)
);
lines.push(this.indent('self.create_widgets()'));
lines.push('');
this.indentLevel = 1;
lines.push(this.indent('def create_widgets(self):'));
this.indentLevel = 2;
designData.widgets.forEach((widget) => {
console.log('[Generator] Widget:', widget.id, widget.type);
lines.push(...this.generateWidgetCode(widget, nameMap));
});
this.indentLevel = 1;
lines.push(this.indent('def run(self):'));
this.indentLevel = 2;
lines.push(this.indent('self.root.mainloop()'));
lines.push('');
const hasEvents = designData.events && designData.events.length > 0;
if (hasEvents) {
console.log('[Generator] Generating event handlers');
lines.push(
...generateEventHandlers(
designData,
(text) => indentText(1, text),
(text) => indentText(2, text)
)
);
}
this.indentLevel = 0;
lines.push('if __name__ == "__main__":');
this.indentLevel = 1;
lines.push(this.indent('try:'));
this.indentLevel = 2;
lines.push(this.indent(`app = ${className}()`));
lines.push(this.indent('app.run()'));
this.indentLevel = 1;
lines.push(this.indent('except Exception as e:'));
this.indentLevel = 2;
lines.push(this.indent('import traceback'));
lines.push(this.indent('traceback.print_exc()'));
lines.push(this.indent('input("Press Enter to exit...")'));
return lines.join('\n');
}
public generateCreateWidgetsBody(designData: DesignData): string {
this.designData = designData;
const lines: string[] = [];
const nameMap = generateVariableNames(designData.widgets);
this.indentLevel = 2; // Inside class -> inside create_widgets
designData.widgets.forEach((widget) => {
lines.push(...this.generateWidgetCode(widget, nameMap));
});
return lines.join('\n');
}
public generateEventHandler(event: any): string {
// Simple helper to generate a single event handler method
// Used when injecting new methods
const lines: string[] = [];
// Use existing signature if available (to preserve user arguments)
// Note: signature does not include 'def ' prefix in our current extraction logic?
// Let's check base.py: sig_raw = self.source_code[def_start_idx : start_idx].strip()
// It includes 'def name(...)'.
// So we just use it directly.
if (event.signature) {
lines.push(` ${event.signature}:`);
} else {
lines.push(` def ${event.name}(self, event=None):`);
}
if (event.code) {
const codeContent =
typeof event.code === 'string'
? event.code
: String(event.code || '');
const body = codeContent
.split('\n')
.map((l: string) => ' ' + l)
.join('\n');
lines.push(body);
} else {
lines.push(' pass');
}
return lines.join('\n');
}
private generateWidgetCode(
widget: WidgetData,
nameMap: Map<string, string>
): string[] {
const lines: string[] = [];
const varName = getVariableName(widget, nameMap);
const widgetType = getWidgetTypeForGeneration(widget.type);
lines.push(
this.indent(
`self.${varName} = ${widgetType}(self.root${getWidgetParameters(widget)})`
)
);
const placeParams = getPlaceParameters(widget);
lines.push(this.indent(`self.${varName}.place(${placeParams})`));
const contentLines = generateWidgetContent(widget, varName);
if (contentLines.length > 0) {
contentLines.forEach((l) => lines.push(this.indent(l)));
}
lines.push(
...getWidgetEventBindings(
this.designData,
widget,
varName,
(text) => this.indent(text)
)
);
return lines;
}
private indent(text: string): string {
return indentText(this.indentLevel, text);
}
}