====== Extension-Entwicklung ======
Anleitung zur Entwicklung neuer Extensions für WvdS FPC RAD Studio.
===== Voraussetzungen =====
* Entwicklungsumgebung eingerichtet (siehe [[.:start|Start]])
* Grundverständnis von Pascal und pas2js
* Vertrautheit mit VSCode Extension API
===== Neue Extension erstellen =====
==== 1. Verzeichnisstruktur anlegen ====
cd sources/extensions
mkdir -p wvds.vscode.{name}/pas
mkdir -p wvds.vscode.{name}/dist
mkdir -p wvds.vscode.{name}/images
==== 2. package.json erstellen ====
{
"name": "wvds-vscode-{name}",
"displayName": "WvdS VSCode {Name}",
"description": "{Beschreibung}",
"version": "0.1.0",
"publisher": "wvds",
"license": "MIT",
"icon": "images/icon.png",
"engines": {
"vscode": "^1.85.0"
},
"categories": ["Other"],
"main": "./dist/extension_main.js",
"activationEvents": [
"onCommand:wvds.{name}.{command}"
],
"contributes": {
"commands": [
{
"command": "wvds.{name}.{command}",
"title": "{Title}",
"category": "WvdS"
}
]
},
"extensionDependencies": [
"wvds.wvds-vscode-core"
]
}
==== 3. build.cfg erstellen ====
[build]
compiler=pas2js
target=vscode-extension
entry=extension_main.pas
[pas2js]
options=-Jc -Jirtl.js -Tbrowser
units=../../common;./pas
output=./dist
==== 4. extension_main.pas erstellen ====
unit extension_main;
{$mode objfpc}{$H+}
interface
uses
JS,
VSCode.API,
VSCode.Commands,
VSCode.ExtensionExports,
WvdS.VSCode.Strings;
procedure Activate(AContext: TExtensionContext);
procedure Deactivate;
implementation
procedure Handle{Command}(Args: TJSValueDynArray);
begin
ShowInfoMessage(rs{Name}CommandExecuted);
end;
procedure DoActivate(AContext: TExtensionContext);
begin
// Command registrieren
RegisterCommand('wvds.{name}.{command}', @Handle{Command});
// Logging
LogInfo('WvdS {Name} Extension aktiviert');
end;
procedure Activate(AContext: TExtensionContext);
begin
try
DoActivate(AContext);
except
on E: Exception do
begin
LogError(rsActivationFailed, [E.Message]);
ShowErrorMessage(Format(rsActivationFailed, [E.Message]));
end;
end;
end;
procedure Deactivate;
begin
LogInfo('WvdS {Name} Extension deaktiviert');
end;
initialization
ExportActivateDeactivate(@Activate, @Deactivate);
end.
==== 5. Kompilieren ====
cd sources/extensions/wvds.vscode.{name}
pas2js -Jc -Jirtl.js -Tbrowser \
-Fu../../common \
-Fu./pas \
-FE./dist \
pas/extension_main.pas
==== 6. Testen ====
- F5 in VS Code -> Extension Development Host
- Befehlspalette -> ''WvdS: {Command}''
===== Schichtenarchitektur einhalten =====
==== Model Layer ====
// {Feature}.Models.pas
unit {Feature}.Models;
{$mode objfpc}{$H+}
interface
type
// Records für Datenstrukturen
TWvdS{Feature}Config = record
Name: string;
Value: Integer;
Enabled: Boolean;
end;
// Enums
TWvdS{Feature}Status = (
fsIdle,
fsProcessing,
fsCompleted,
fsFailed
);
// Arrays
TWvdS{Feature}ConfigArray = array of TWvdS{Feature}Config;
implementation
end.
==== Service Layer ====
// {Feature}.Service.pas
unit {Feature}.Service;
{$mode objfpc}{$H+}
interface
uses
{Feature}.Models;
// Validierung
function ValidateConfig(const AConfig: TWvdS{Feature}Config): Boolean;
// Operationen
function ProcessData(const AInput: string): TWvdS{Feature}Config;
// Business-Logik
procedure ExecuteFeature(const AConfig: TWvdS{Feature}Config);
implementation
uses
SysUtils, WvdS.VSCode.Strings;
function ValidateConfig(const AConfig: TWvdS{Feature}Config): Boolean;
begin
Result := (AConfig.Name <> '') and (AConfig.Value >= 0);
end;
function ProcessData(const AInput: string): TWvdS{Feature}Config;
begin
// Verarbeitung...
Result.Name := AInput;
Result.Value := Length(AInput);
Result.Enabled := True;
end;
procedure ExecuteFeature(const AConfig: TWvdS{Feature}Config);
begin
if not ValidateConfig(AConfig) then
raise Exception.Create(rs{Feature}InvalidConfig);
// Ausführung...
end;
end.
==== Extension Layer ====
// extension_main.pas - nur UI-Logik
procedure HandleCommand(Args: TJSValueDynArray);
var
Config: TWvdS{Feature}Config;
begin
// 1. Input aus UI holen
Config := GetConfigFromDialog;
// 2. Service aufrufen (keine Logik hier!)
if not ValidateConfig(Config) then
begin
ShowWarningMessage(rs{Feature}InvalidInput);
Exit;
end;
// 3. Ausführung
try
ExecuteFeature(Config);
ShowInfoMessage(rs{Feature}Success);
except
on E: Exception do
ShowErrorMessage(Format(rs{Feature}Failed, [E.Message]));
end;
end;
===== VSCode API verwenden =====
==== Commands ====
// Command registrieren
RegisterCommand('wvds.{name}.{action}', @Handler);
// Command ausführen
ExecuteCommand('wvds.other.action');
==== Configuration ====
// Einstellung lesen
var Value := GetConfiguration('wvds.{name}').Get('setting', DefaultValue);
// Einstellung schreiben
GetConfiguration('wvds.{name}').Update('setting', NewValue, True);
==== Output Channel ====
// Channel nutzen (von Core bereitgestellt)
LogInfo('Information');
LogWarn('Warnung');
LogError('Fehler: %s', [E.Message]);
LogDebug('Debug: %s', [Data]); // Nur bei logLevel=debug
==== WebView ====
// WebView Panel erstellen
var Panel := CreateWebviewPanel(
'wvds.{name}.view',
'Titel',
ViewColumn.One,
WebviewOptions
);
// HTML setzen
Panel.Webview.Html := LoadTemplate('template.html');
// Messaging
Panel.Webview.OnDidReceiveMessage(@HandleMessage);
Panel.Webview.PostMessage(JSObject);
==== StatusBar ====
var StatusItem := CreateStatusBarItem(StatusBarAlignment.Left, 100);
StatusItem.Text := '$(check) Ready';
StatusItem.Show;
===== Resourcestrings für i18n =====
**Alle Strings müssen in WvdS.VSCode.Strings.pas definiert werden!**
// WvdS.VSCode.Strings.pas
resourcestring
// {Feature} Extension
rs{Feature}CommandExecuted = '{Feature} command executed';
rs{Feature}InvalidConfig = 'Invalid configuration';
rs{Feature}Success = 'Operation completed successfully';
rs{Feature}Failed = 'Operation failed: %s';
// WvdS.VSCode.Strings.DE.pas
resourcestring
rs{Feature}CommandExecuted = '{Feature}-Befehl ausgeführt';
rs{Feature}InvalidConfig = 'Ungültige Konfiguration';
rs{Feature}Success = 'Vorgang erfolgreich abgeschlossen';
rs{Feature}Failed = 'Vorgang fehlgeschlagen: %s';
===== Error Handling =====
procedure SafeOperation;
begin
try
DoRiskyOperation;
except
on E: EFileNotFoundException do
begin
LogError(rsFileNotFound, [E.FileName]);
ShowErrorMessage(Format(rsFileNotFound, [E.FileName]));
end;
on E: EAccessDenied do
begin
LogError(rsAccessDenied, [E.Path]);
ShowErrorMessage(Format(rsAccessDenied, [E.Path]));
end;
on E: Exception do
begin
LogError(rsUnexpectedError, [E.ClassName, E.Message]);
ShowErrorMessage(Format(rsUnexpectedError, [E.ClassName, E.Message]));
end;
end;
end;
**Niemals leere except-Blöcke!** Jede Exception muß behandelt oder geloggt werden.
===== Debug-Logging =====
{$IFDEF DEBUG}
procedure LogDebugTrace(const AMessage: string);
begin
if not DebugEnabled then Exit;
WriteDebugLog(Format('[%s] %s at %s', [
FormatDateTime('hh:nn:ss.zzz', Now),
AMessage,
GetCallStack
]));
end;
{$ENDIF}
Aktivierung:
* Kompilieren mit ''-dDEBUG''
* Extension starten mit ''--debug'' Parameter
===== Tests schreiben =====
// Tests/{Feature}.Tests.pas
unit {Feature}.Tests;
{$mode objfpc}{$H+}
interface
uses
TestFramework, {Feature}.Service, {Feature}.Models;
type
T{Feature}ServiceTest = class(TTestCase)
published
procedure TestValidateConfig_ValidInput_ReturnsTrue;
procedure TestValidateConfig_EmptyName_ReturnsFalse;
procedure TestProcessData_ValidInput_ReturnsConfig;
end;
implementation
procedure T{Feature}ServiceTest.TestValidateConfig_ValidInput_ReturnsTrue;
var
Config: TWvdS{Feature}Config;
begin
Config.Name := 'Test';
Config.Value := 42;
Config.Enabled := True;
CheckTrue(ValidateConfig(Config));
end;
procedure T{Feature}ServiceTest.TestValidateConfig_EmptyName_ReturnsFalse;
var
Config: TWvdS{Feature}Config;
begin
Config.Name := '';
Config.Value := 42;
CheckFalse(ValidateConfig(Config));
end;
end.
===== Checkliste vor Commit =====
[ ] SoC eingehalten (Model/Service/Extension getrennt)
[ ] Keine hardcodierten Strings (resourcestrings verwendet)
[ ] Übersetzungen vorhanden (EN, DE, SL, HR)
[ ] Error Handling implementiert
[ ] Debug-Logging hinzugefügt
[ ] Sicherheitsrichtlinien geprüft
[ ] Unit-Tests geschrieben
[ ] README aktualisiert
[ ] CHANGELOG aktualisiert
===== Siehe auch =====
* [[.:architektur|Architektur-Übersicht]]
* [[.:code-konventionen|Code-Konventionen]]
* [[.:sicherheit|Sicherheitsrichtlinien]]
* [[.:testing|Testing]]
* [[.:vscode-wrapper|VSCode Wrapper API]]