Nahaufnahme einer Person, die mit einem Tablet auf einer Decke sitzt.

TechWiese Blog

Visual Studio Code (4): Extensions entwickeln

8. Dezember 2015

Portrait Bild von Tobias Kahlert

Tobias Kahlert

Dieser Blogbeitrag ist ein Repost und stammt im Original aus dem Know-how-Bereich von TechWiese, dessen Artikel in diesem Blog aufgegangen sind.

Wie im letzten Artikel "Open Source und erweiterbar – Extensions installieren!" beschrieben, lässt sich Visual Studio Code (VS Code) ab sofort mit Extensions erweitern. Diese können aus dem Marketplace heruntergeladen oder selbst entwickelt werden. Das beste Tool für die Entwicklung dieser Erweiterungen ist VS Code selber. Es bietet von Haus aus die Möglichkeit, Extensions zu erstellen, zu debuggen und zu testen. Im Folgenden stelle ich die Standard-Extensions vor und zeige die Entwicklung einer Erweiterung.

Das Erweiterungsmodell von Visual Studio Code

Im Gegensatz zu anderen Editoren, die auch erweiterbar sind, haben Extensions bei VS Code nicht die volle Kontrolle über den Editor, sondern werden in einem separaten Prozess ausgeführt und können von dort nur über bereitgestellte Schnittstellen mit diesem kommunizieren. Das hat den Nachteil, dass nur die Bereiche des Editors erweitert oder verändert werden können, für die auch eine Schnittstelle zur Verfügung steht. Andererseits ist die Stabilität und die Performanz von VS Code dadurch unabhängig von den Erweiterungen. So bleibt zum Beispiel die Ladezeit des Editors relativ kurz, auch wenn viele Extensions installiert sind. Ein Grund dafür ist, dass Extensions erst dann geladen werden, wenn bestimmte Ereignisse – so genannte Activation Events – auftreten.

Solche können zum Beispiel das Ausführen eines Kommandos oder das Laden einer Datei, in der eine bestimmte Programmiersprache erkannt wird, sein. Sie werden im Manifest der Extension angegeben. Dort werden auch Contribution Points definiert, die es der Extension ermöglichen, Inhalte wie Konfigurationsoptionen, Tastenkürzel, Kommandos und Syntaxdateien für Programmiersprachen zu registrieren. Diese tauchen dann an den entsprechenden Punkten in VS Code auf.

Node.js

Erweiterungen sind besondere Node.js-Module. Dies eröffnet die Möglichkeit, bereits existente Module über npm zu installieren und zu verwenden. Node.js muss daher installiert und über $PATH verfügbar sein.

Anlegen einer ersten Extension

Im Folgenden zeige ich die Entwicklung einer kleinen Erweiterung, die ein Kommando Hello World registriert und „Hello World“ in einer kleinen Infobox anzeigt, wenn das Kommando ausgeführt wird.

Zunächst muss dafür ein Projekt für die Extension angelegt werden. Am einfachsten geht das mit dem Yeoman VS Code Projektgenerator. Dieser kann einfach über die Eingabeaufforderung installiert und gestartet werden:

npm install -g yo generator-code
yo code

Es erscheint der Yeoman Projekt-Wizard, über den der Typ der Extension sowie verschiedene Rahmendaten, wie der Name des Autors und der Extension, angegeben werden können. Als Typ wird in unserem Beispiel eine TypeScript Extension gewählt. Der TypeScript Code wird später zwar auch in JavaScript Code umgewandelt, aber TypeScript bringt einige nette Features wie statische Typisierung mit sich, die gewisse Fehlerquellen bereits vor der Ausführung aufdecken.

The command generator

Abbildung 1: Yeoman beim Erstellen einer VS Code Extension
Abbildung 1: Yeoman beim Erstellen einer VS Code Extension

Yeoman hat nun eine neue Extension erzeugt, die mit Visual Studio Code geöffnet werden kann. Dazu muss man im Explorer auf den neu erstellten Ordner mit der rechten Maustaste klicken und „Open with Code“ auswählen. Alternativ kann in VS Code oben im Menü File -> Open Folder… genutzt werden.

Ausführen der Extension

Die Extension ist nun bereits ausführbar. Zum Starten auf der linken Seite auf das Debug-Symbol und dann auf den grünen Pfeil klicken oder einfach F5 drücken. Es öffnet sich eine neue VS Code-Instanz, in der auch die neue Extension geladen wird (am „Extension Development Host“ in der Titelleiste zu erkennen). Um Extensions zu debuggen, können im Code der Extension ganz normal Breakpoints gesetzt werden. Die neue Instanz hält dann an den entsprechenden Punkten an.

Running VS Code with an extension

Abbildung 2: Debuggen der Beispiel-Extension
Abbildung 2: Debuggen der Beispiel-Extension

Mit F1 lässt sich im neuen Fenster die Kommandopalette öffnen und durch Eingabe von „Hello World“ der entsprechende Eintrag finden. Mit Enter kann dieser ausgeführt werden. Es erscheint eine Infobox mit dem Text „Hello World!“.

Aufbau der Extension

Ohne dass wir eine Zeile programmieren mussten, hat Yeoman uns eine fertige Extension in einem neuen Verzeichnis erzeugt. Die Struktur von Erweiterungen ist immer gleich und sieht auf Dateiebene wie folgt aus:

.
├── .gitignore
├── .vscode                     // Dateien für die Integration in VS Code
│   ├── launch.json
│   ├── settings.json
│   └── tasks.json
├── .vscodeignore
├── README.md
├── src                         // Quelldatein der Extension 
│   └── extension.ts            // Hauptdatei der Extension
├── test                        // Tests
│   ├── extension.test.ts       // Tests für extension.ts
│   └── index.ts                // Hauptdatei für Tests
├── node_modules
│   ├── vscode                  // language services
│   └── typescript              // Kompiler for typescript
├── out                         // Zielordner für die von TypeScript erzeugten JavaScript Datein
│   ├── src
│   |   ├── extension.js
│   |   └── extension.js.map
│   └── test
│       ├── extension.test.js
│       ├── extension.test.js.map
│       ├── index.js
│       └── index.js.map
├── package.json                // Manifest der Extension
├── tsconfig.json               // Projektdatei mit Optionen für TypeScript. 
├── typings                     // Typdefinitionen von Bibliotheken für TypeScript
│   ├── node.d.ts               // Node.js APIs
│   └── vscode-typings.d.ts     // VS Code APIs
└── vsc-extension-quickstart.md // Quickstartguide

package.json

Jede Extension hat eine package.json-Datei im Hauptverzeichnis. Diese hat zwei Aufgaben: einerseits ist sie das Manifest der Extension und definiert allerlei wichtige Eckdaten und andererseits ist sie die Projektdatei für Node.js. Viele der eingetragenen Felder haben einen Effekt auf das Ausführen der Extension oder auf ihre Präsentation im Marketplace. Die package.json-Datei der Beispielerweiterung könnte folgendermaßen aussehen:

{
  "name": "BeispielExtension",
  "description": "",
  "version": "0.0.1",
  "publisher": "Du",
  "engines": {
    "vscode": "^0.10.1"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [
    "onCommand:extension.sayHello"
  ],
  "main": "./out/src/extension",
  "contributes": {
    "commands": [{
      "command": "extension.sayHello",
      "title": "Hello World"
    }]
  },
  "scripts": {
    "vscode:prepublish": "node ./node_modules/vscode/bin/compile",
    "compile": "node ./node_modules/vscode/bin/compile -watch -p ./"
  },
  "devDependencies": {
    "typescript": "^1.6.2",
    "vscode": "0.10.x"
  }
}

Die package.json-Datei jeder Extension wird beim Start von VS Code gelesen und der Editor reagiert auf die unterschiedlichen Contribution Points und Activation Events. In diesem Fall wird ein Kommando mit dem Text „Hello World“ in die Kommandopalette eingetragen. Die Extension selber (insbesondere der hinterlegte Code) wird erst dann geladen, wenn dieser Befehl ausgeführt wird. Das ist an dem Eintrag „activationEvents“ zu erkennen. Im Eintrag „main“ ist der Einstiegspunkt für die Extension definiert. In diesem Fall handelt es sich um die JavaScript-Datei, die aus der TypeScript-Datei „extension.ts“ erzeugt wurde. Diese enthält die „activate“-Funktion, die aufgerufen wird, wenn die Extension geladen wird.

extension.ts

// Das Modul 'vscode' enthält die API um mit VS Code zu kommunizieren
// Hier wird es importiert und mit 'vscode' referenziert
import * as vscode from 'vscode';

// Die 'activate' Funktion ist der Haupteinstiegspunkt.
// Sie wird aufgerufen, wenn das im Manifest angegebene Kommando ausgeführt wird
export function activate(context: vscode.ExtensionContext) {

  // Gebe ein paar Informationen auf der Konsole aus.
  // Diese Zeile wird nur einmal bei der Aktivierung der Extension aufgerufen.
  console.log('Congratulations, your extension "  BeispielExtension" is now active!');
 
  // Das Kommando wurde in der package.json definiert.
  // Eine Implementierung kann mit 'registerCommand' angegeben werden.
  // Der commandId Parameter muss mit dem 'command' Feld in der package.json übereinstimmen.
  var disposable = vscode.commands.registerCommand('extension.sayHello', () => {
    // Der nachfolgende Code wird immer ausgeführt, wenn das Kommando aufgerufen wird.
    // Zeige 'Hello World!' in einer kleinen Box an.
    vscode.window.showInformationMessage('Hello World!');
  });
context.subscriptions.push(disposable);
}

README.md

README.md ist besonders im Zusammenhang mit der Publizierung der Extension von Bedeutung. Der Inhalt ist in Markdown geschrieben und wird im Marketplace angezeigt, wenn die Details zu der Extension abgefragt werden.

CodeBing

Zu Demonstrationszwecken habe ich für euch eine kleine Extension programmiert und veröffentlicht: CodeBing. Mit dieser kann direkt aus VS Code heraus bei einer beliebigen Suchmaschine gesucht werden. Der Benutzer soll einen Text im Editor markieren können, dann über ein Tastenkürzel oder die Eingabe des Befehls „Bing“ die Extension aufrufen, die den markierten Text noch einmal in einer Eingabebox anzeigt und nach einer Bestätigung mit Enter bei Bing oder einer anderen Suchmaschine sucht.

Die Extension ist sowohl im Marketplace als auch auf Github zu finden und befindet sich unter ständiger Weiterentwicklung. Dieser Artikel bezieht sich auf die Version v0.0.1.

package.json

Zunächst musste package.json angepasst werden. Einerseits liest der Marketplace einige Felder aus und zeigt die eingetragenen Links an der rechten Seite auf der Detailansicht der Extension an. Dazu gehört zum Beispiel der Link zum Git-Repository. Andererseits soll das Suchkommando nicht „Hello Word“ heißen. Hier die wichtigsten Felder:

{
  "displayName": "CodeBing",
  "homepage": "https://github.com/SrTobi/code-bing/",
  "activationEvents": ["onCommand:codebing.search"],
  "main": "./out/src/extension",
  "contributes": {
   "commands": [
    {
      "command": "codebing.search",
      "title": "Bing"
    }
   ]
  }
},
  "repository": {
    "type": "git",
    "url": "https://github.com/SrTobi/code-bing/"
  },
  "bugs": {
    "url": "https://github.com/SrTobi/code-bing/issues"
  }
}

Mehr Input mit der Eingabebox

Der nächste Schritt ist dann auch schon die Implementierung des Kommandos. Dieser soll zunächst eine Eingabebox öffnen, in der der Text noch einmal geändert werden kann. Danach soll die Funktion „searchfor“ mit dem zu suchenden Text aufgerufen werden. Zunächst der Code für das Öffnen der Eingabebox innerhalb der „activate“-Funktion:

var disposable = vscode.commands.registerCommand('codebing.search', () => {
  // 'vscode' ist die Schnittstelle zwischen der Extension und VS Code
  // Zunächst holen wir uns den Text-Editor, der aktuell vom Benutzer ausgewählt ist.
  // Sollte der Benutzer keinen ausgewählt haben, brechen wir gleich ab. 
  let editor = vscode.window.activeTextEditor;
  if (!editor) {
    return; // No open text editor
  }
 
  // Danach können wir den selektierten Text auslesen
  let selection = editor.selection;
  let text = editor.document.getText(selection);
 
  // Nun soll eine Eingabebox erscheinen, in dem der Benutzer die Suche verändern kann.
  // Dazu sind zunächst ein paar Optionen einzustellen
  let options:vscode.InputBoxOptions = {
    prompt: "Enter something to search for",    // <- Der Hilfstext der unter dem Eingabefeld erscheinen soll.
    value: text,                                // <- Der Text der schon im Eingabefeld voreingestellt sein soll.
    placeHolder: "Query"                        // <- Ein Text, der ausgegraut im Eingabefeld angezeigt wird.
  }
 
  // Mit den angegeben Optionen kann nun die Eingabebox geöffnet werden.
  // Das Ergebnis davon ist nicht der eingegebene Text, sondern ein Objekt vom Typ Thenable<string>.
  // Mit diesem Objekt kann nun eine Funktion angegeben werden, die ausgeführt wird,
  // wenn der Benutzer die Eingabebox mit Enter bestätigt.
  // Die angegebe Funktion bekommt den eingegebenen Text als Argument übergeben.
  // Genau diese Signatur wird später die searchfor Funktion kaben.
  vscode.window.showInputBox(options).then(searchfor);
});
Abbildung 3: Die Eingabebox von CodeBing
Abbildung 3: Die Eingabebox von CodeBing

Browser öffnen mit einem externen Node.js-Modul

Um die „searchfor“-Funktion zu implementieren, benötigt man einen Weg, den Standardbrowser mit einer Url zu öffnen. Zum Glück können VS Code Extensions jedes beliebige Node.js-Modul nutzen, nachdem es mit npm installiert wurde. Wir installieren also das Modul open, das das Öffnen des Browsers übernimmt.

npm install open --save

open ist eine reine JavaScript-Bibliothek und enthält somit keine Typ-Informationen. Damit man trotzdem Unterstützung für open in VS Code bekommt, kann man die Typ-Informationen nachträglich mit „tsd“ hinzufügen. Es wird also „tsd“ über npm installiert und dann die Typ-Definitionen für open heruntergeladen:

npm install -g tsd
tsd install open

Contribution Point für Einstellungen

Mit open kann nun der Browser mit einer bestimmten Url geöffnet werden. Für Bing wäre diese URL zum Beispiel „https://www.bing.com/search?q={query}“, wobei „{query}“ mit dem eigentlichen Suchtext ersetzt wird. Um jede beliebige Suchmaschine zu unterstützen, soll dem Benutzer die Möglichkeit gegeben werden, diese URL in seinen VS Code-Einstellungen selber zu setzen. Dazu wird der neue Contribution Point „configuration“ mit der Einstellung „codebing.searchprovider” hinzugefügt:

"configuration": {
  "title": "CodeBing Configuration",
  "type": "object",
  "properties": {
    "codebing.searchprovider": {
      "type": "string",
      "default": "https://www.bing.com/search?q={query}",
      "description": "A string to an online search engine. {query} is replaced with the user search."
    }
  }
}

Durch das Hinzufügen des Contribution Point bekommt der Benutzer in seinen Einstellungen Informationen zu dieser Option angezeigt. Hat der Benutzer nichts angegeben, tritt der angegebene Standardwert an diese Stelle.

Abbildung 4: VS Code schlägt Optionen vor, die in Contribution Points definiert sind.
Abbildung 4: VS Code schlägt Optionen vor, die in Contribution Points definiert sind.
Abbildung 5: Fährt man mit der Maus über die Einstellung, wird eine Erklärung eingeblendet
Abbildung 5: Fährt man mit der Maus über die Einstellung, wird eine Erklärung eingeblendet

Die searchfor-Funktion

Nun kann die eigentliche „searchfor“-Funktion implementiert werden. Sie liest die obige Einstellung aus, ersetzt „{query}“ mit dem Suchtext und öffnet den Browser.

function searchfor(query:string) {
         // Hole alle Einstellungen für codebing
         let config = vscode.workspace.getConfiguration("codebing");
         // Lese searchprovider aus den Einstellungen aus
         let searchprovider = config.get("searchprovider") as string;
         // Entferne alle Zeilenumbrüche aus der Eingabe
         let q = query.replace(/[\r\n]/g, "");
         // Ersetze {query} mit der Eingabe
         let url = searchprovider.replace("{query}", q);
         
         // Öffne den Browser mit der URL
         open(url);
}

Die Extension funktioniert und kann, wie oben gezeigt, getestet werden.

Abbildung 6: Beispiel für die Nutzung von CodeBing
Abbildung 6: Beispiel für die Nutzung von CodeBing

Kommandoaufruf mit Tastenkürzeln

Zusätzlich zu der Nutzung von „Bing“ aus der Kommandopalette heraus soll der Benutzer die Extension auch mit einem Tastenkürzel aktivieren können. Er kann ein solches natürlich selber in seinen persönlichen Tastenkürzeln definieren, die Erweiterung soll aber Out-of-the-Box eine Tastenkombination unterstützen: Strg+Alt+F. Zum Glück ist die Implementierung ganz einfach, es muss nur ein weiterer Contribution Point hinzugefügt werden:

"keybindings": [
  {
    "command": "codebing.search",
    "key": "ctrl+alt+F"
  }
]

Jetzt funktioniert das Tastenkürzel. Der Benutzer kann es in seinen Einstellungen immer noch ändern oder überschreiben.

Publizieren

Nachdem die Extension soweit fertig ist, soll sie natürlich auch mit der Welt geteilt werden. Dazu wird sie im Marketplace veröffentlicht.

Vor der Veröffentlichung muss das README noch angepasst werden, damit der potenzielle Benutzer sehen kann, was er für eine Erweiterung installieren wird. Zusätzlich zur Beschreibung habe ich auch noch zwei Bilder hinzugefügt, die die Benutzung veranschaulichen. In der package.json sollte außerdem im Feld „icon“ der Pfad zu einem 128x128 Pixel großen Bild angegeben werden. Dieses wird dann im Marketplace als Icon verwendet.

Nun muss das Veröffentlichungstool „vsce“ über npm installiert werden:

					npm install -g vsce
				

Dieses wird voll automatisch die Veröffentlichung durchführen. Dazu benötigt es einen Personal Access Token, welcher über die Visual Studio Team Services-Website erstellt werden kann. Wie man dabei genau dabei vorgeht, steht hier. Danach kann die Extension mit vsce veröffentlicht werden:

vsce publish
				
Abbildung 7: CodeBing wird veröffentlicht
Abbildung 7: CodeBing wird veröffentlicht

Das Ergebnis ist unter https://marketplace.visualstudio.com/items/SirTobi.code-bing zu sehen.

Nächste Schritte

Das Entwickeln von Extensions für VS Code ermöglicht die einfache Erweiterung des Editors. Durch das API-Modell und den fehlenden Zugriff auf die darunterliegende DOM-Struktur ist nicht immer jede Extension möglich. Deshalb ist es wichtig, sich vorher in der Dokumentation darüber zu informieren, ob die geplante Extension überhaupt umsetzbar ist. VS Code befindet sich aber noch in der Beta-Phase und Erweiterungen der API sind immer möglich, sodass in Zukunft durchaus weitere Erweiterungstypen möglich sein könnten.

Mit dem Yeoman Extension Generator ist das Anlegen neuer Extensions kinderleicht und mit dem Zugriff auf sämtliche Node.js-Module eröffnet sich ein riesiges Universum möglicher Erweiterungen. Die meisten veröffentlichten Extensions sind als Open Source im Internet zu finden (z.B. auf GitHub) und können dabei helfen, Probleme zu lösen, auf die auch schon andere gestoßen sind. Für weitere Fragen, gibt es den Tag vscode auf StackOverflow.

Und jetzt viel Spaß beim Entwickeln neuer Extensions.

Wichtige Ressourcen

Visual Studio Code Artikelserie

Grundlagen

Extensions

Weitere Beiträge