Contexte
Ça fait quelques mois maintenant que je fais tourner OpenVSCode Server sous
NixOS sur tous mes serveurs.
Mais, ces derniers temps, il devenait de plus en plus lourd et instable.
Certaines fonctionnalités cessaient de fonctionner silencieusement, avec des effets problématiques dans l’interface web. J’ai découvert ultérieurement que les lenteurs étaient dues à des modules node qui restaient en mémoire, consommant mon CPU jusqu’à 100 % et plusieurs gigaoctets de mémoire !
Las, j’ai décidé d’aller voir ailleurs si l’herbe était plus verte. Grand bien m’en a pris.
J’ai commencé par chercher une solution qui aurait fait plaisir à mon ami Olivier : les éditeurs de code avec une
TUI.
Dans mon environnement, il me suffisait de lancer un serveur ttyd (que j’utilise de toute façon pour mon administration quotidienne), dans lequel j’aurai lancé mon éditeur.
Malgré de bonnes surprises (comme Fresh), la TUI m’est trop inconfortable.
Je m’attendais à de meilleures options de personnalisation de l’interface et du comportement, sans aller jusqu’à un neovim (d’autant que je ne me suis jamais fait aux raccourcis clavier).
En outre, le manque de plugins se fait cruellement sentir.
Même son de cloche avec
TermIDE et
Micro.
Je ne leur reproche rien : ce sont d’excellents outils, chacun en fonction de sa maturité. Mais aucun ne m’a mis à l’aise. Aucun ne m’a fait me sentir «chez moi».
De VSCode à Theia
J’ai alors décidé de redonner sa chance à Theia, que je teste régulièrement avec plus ou moins de succès, et dont j’ai déjà parlé dans mes colonnes1.
Cette fois, je me suis fait aider par ChatGPT, et je re-découvre la puissance de ce «framework de développement d’IDE».
Mais avant d’aller plus loin, il me parait utile de préciser certaines choses à propos de
VSCode, OpenVSCode Server et Theia, afin d’apporter un peu de contexte, sans aller jusqu’à retracer la filiation complète (ou son absence…).
Au cours de son histoire, Microsoft a réalisé correctement au moins un logiciel : VSCode.
Son
code source est disponible sous
licence MIT, et sa télémétrie est (était) facilement désactivable.
C’était l’un des rares logiciels de l’entreprise à être relativement vertueux.
VSCode a su moderniser l’environnement de développement à une époque où la concurrence était rude, jusqu’à s’imposer comme un outil incontournable.
Mais les choix techniques et philosophiques faits par Microsoft sont de plus en plus douteux au fil du temps, l’environnement de développement grossit, les extensions se professionnalisent (et certaines deviennent payantes). Bref, VSCode se transforme en WordPress du code.
Pour ma part, le problème le plus fondamental de VSCode réside en son usage de technologies web (javascript, HTML et CSS), résultant de la mouvance ancienne de convergence entre le web et le desktop. Malgré cela, Microsoft réserve l’usage de VSCode dans un navigateur à des clients professionnels.
Or, comme le code source est disponible sous licence MIT, certains (dont GitPod, désormais Ona, qui fourni OpenVSCode Server) ont contourné la limitation imposée par Microsoft, afin de fournir un environnement de développement nativement basé sur le web.
Cela a ses avantages : on bénéficie de l’infrastructure de code de VSCode (ce qui inclue donc le support natif de presque toutes les extensions), sans perdre ses habitudes.
Mais, on embarque aussi les travers de VSCode accumulés au fil du temps.
Theia a opté pour une autre approche.
Theia n’est pas un fork de VSCode.
Il utilise certains composants (notamment l’éditeur Monaco), mais la fondation
Eclipse à l’origine du projet a bien écrit son propre code, afin de proposer non pas un environnement de développement, mais un véritable framework de création d’environnement de développement, hautement paramétrable et modulaire, y compris dans les éléments d’interface.
Certes, l’UI ressemble à celle de VSCode, parce que l’UI/UX de VSCode est plutôt satisfaisante, mais le code de Theia est bien celui d’Eclipse, et non celui de Microsoft.
Techniquement, il supporte ainsi tout ce que l’on peut attendre d’un IDE moderne : protocoles
LSP et DAP (tous deux hérités de Microsoft Visual Studio, l’environnement de développement historique de Microsoft avant VSCode), implémentation d’une API d’extensions similaire à celle de VSCode reposant sur le store
Open VSX, une palette de commandes, etc.
Mais on peut choisir d’intégrer (ou non) ces fonctionnalités (au contraire de VSCode).
Et, comme on va le voir, c’est beaucoup moins compliqué que ce qu’on pourrait penser.
Notons d’ailleurs que Theia fournit son propre IDE, basé sur Theia Platform que j’utilise pour construire mon propre environnement.
Un framework pour environnements de développement
Étant une application node, les dépendances se déclarent dans le traditionnel package.json.
C’est également ici qu’on va pouvoir déclarer la configuration souhaitée pour l’environnement de développement.
Et c’est bien cet aspect déclaratif qui fait la force de Theia par rapport à VSCode.
Comme j’ai besoin d’un IDE sur pas moins de six serveurs, ça me change la vie par rapport à la configuration d’OpenVSCode Server, d’autant que je pourrais créer six IDE différents en fonction de mes besoins, juste avec un seul fichier (le package.json).
{
"name": "minimal-theia-ide",
"version": "0.1.0",
"private": true,
"engines": {
"node": ">=22 <=24"
},
"theia": {
"target": "browser",
"backend": {
"config": {
"frontendConnectionTimeout": 3000
}
},
"frontend": {
"config": {
"reloadOnReconnect": true,
"applicationName": "Minimal Theia IDE",
"preferences": {
"git.decorations.enabled": true,
"explorer.decorations.colors": true,
"files.enableTrash": false,
"security.workspace.trust.enabled": false,
"workbench.colorTheme": "Bearded Theme Anthracite",
"terminal.integrated.enablePersistentSessions": true,
"workbench.iconTheme": "vscode-icons",
"vsicons.dontShowNewVersionMessage": true,
"grammalecte.pathToGrammalecteCli": "/run/current-system/sw/bin/grammalecte-vscode-cli.py",
"languageToolLinter.external.url": "http://10.0.3.4:32000",
"editor.minimap.enabled": false,
"editor.fontSize": 18,
"editor.wordWrap": "on",
"editor.rulers": [80, 120],
"files.autoSave": "onFocusChange",
"editor.formatOnSave": true,
"go.formatTool": "default",
"yaml.format.enable": false,
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[go]": {
"editor.defaultFormatter": "golang.go"
},
"[toml]": {
"editor.defaultFormatter": "tombi-toml.tombi"
},
"nix.enableLanguageServer": true,
"nix.serverPath": "nixd",
"nix.serverSettings": {
"nixd": {
"formatting": {
"command": ["nixfmt"]
}
}
},
"[nix]": {
"editor.defaultFormatter": "jnoortheen.nix-ide",
"editor.formatOnSave": true
},
"gitignoreHelper.onlyInGitProjects": true,
"workbench.editorAssociations": {
".gitignore": "default"
},
"editor.stickyScroll.enabled": false,
"workbench.tree.enableStickyScroll": false,
"terminal.integrated.stickyScroll.enabled": false,
"breadcrumbs.enabled": false,
"editor.lineHeight": 30,
"git.autofetch": true,
"git.confirmSync": false,
"git.enableSmartCommit": true,
"explorer.compactFolders": false,
"markdown.editor.pasteUrlAsFormattedLink.enabled": "smartWithSelection"
}
}
}
},
"scripts": {
"download:plugins": "theia download:plugins --parallel=false",
"build:product-ui": "npm --workspace extensions/product-ui run build",
"build": "npm run build:product-ui && theia rebuild:browser && theia build --mode development",
"setup": "npm run download:plugins && npm run build",
"start": "theia start --hostname 0.0.0.0 --port 3000 --plugins=local-dir:plugins"
},
"dependencies": {
"@theia/core": "1.73.0",
"@theia/editor": "1.73.0",
"@theia/filesystem": "1.73.0",
"@theia/monaco": "1.73.0",
"@theia/navigator": "1.73.0",
"@theia/preferences": "1.73.0",
"@theia/process": "1.73.0",
"@theia/terminal": "1.73.0",
"@theia/userstorage": "1.73.0",
"@theia/workspace": "1.73.0",
"@theia/plugin-ext-vscode": "1.73.0",
"@theia/scm": "1.73.0",
"@theia/scm-extra": "1.73.0",
"@richard/theia-product-ui": "0.1.0"
},
"devDependencies": {
"@theia/cli": "1.73.0"
},
"overrides": {
"dompurify": "3.4.11"
},
"theiaPluginsDir": "plugins",
"theiaPlugins": {
"vscode.git-base": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.git-base.vsix",
"vscode.git": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.git.vsix",
"vscode.markdown": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.markdown.vsix",
"vscode.markdown-language-features": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.markdown-language-features.vsix",
"vscode.html": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.html.vsix",
"vscode.html-language-features": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.html-language-features.vsix",
"vscode.css": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.css.vsix",
"vscode.css-language-features": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.css-language-features.vsix",
"vscode.javascript": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.javascript.vsix",
"vscode.typescript": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.typescript.vsix",
"vscode.typescript-language-features": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.typescript-language-features.vsix",
"davidlday.languagetool-linter": "https://open-vsx.org/api/davidlday/languagetool-linter/0.25.8/file/davidlday.languagetool-linter-0.25.8.vsix",
"jenselme.grammalecte": "https://open-vsx.org/api/jenselme/grammalecte/0.3.0/file/jenselme.grammalecte-0.3.0.vsix",
"vscode-icons-team.vscode-icons": "https://open-vsx.org/api/vscode-icons-team/vscode-icons/12.18.0/file/vscode-icons-team.vscode-icons-12.18.0.vsix",
"BeardedBear.beardedtheme": "https://open-vsx.org/api/BeardedBear/beardedtheme/10.1.0/file/BeardedBear.beardedtheme-10.1.0.vsix",
"esbenp.prettier-vscode": "https://open-vsx.org/api/esbenp/prettier-vscode/12.4.0/file/esbenp.prettier-vscode-12.4.0.vsix",
"golang.Go": "https://open-vsx.org/api/golang/Go/0.54.0/file/golang.Go-0.54.0.vsix",
"redhat.vscode-yaml": "https://open-vsx.org/api/redhat/vscode-yaml/1.23.0/file/redhat.vscode-yaml-1.23.0.vsix",
"tombi-toml.tombi": "https://github.com/tombi-toml/tombi/releases/download/v1.1.6/tombi-vscode-1.1.6-${targetPlatform}.vsix",
"jnoortheen.nix-ide": "https://open-vsx.org/api/jnoortheen/nix-ide/0.5.9/file/jnoortheen.nix-ide-0.5.9.vsix",
"vscode.json": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.json.vsix",
"vscode.json-language-features": "https://github.com/eclipse-theia/vscode-builtin-extensions/releases/download/1.108.2/vscode.json-language-features.vsix",
"michelemelluso.gitignore": "https://open-vsx.org/api/michelemelluso/gitignore/1.0.1/file/michelemelluso.gitignore-1.0.1.vsix"
},
"workspaces": ["extensions/*"]
}
Ce fichier est hautement personnalisé pour mon usage : il serait inapproprié de le recopier tel quel.
D’autant qu’il mentionne un package qui m’est exclusif, @richard/theia-product-ui, qui contient le code nécessaire aux modifications de l’interface qui me conviennent.
Ces modifications tiennent dans trois fichiers pour un volume total d’environ 500 lignes de code plutôt digeste, surtout pour du javascript, et surtout pour du code produit par ChatGPT.
J’admets que je n’ai pas encore pris en main ce code ; je ne saurai donc pas l’expliquer, même si j’en comprends les grandes lignes.
J’ai néanmoins toute confiance dans ce que ChatGPT m’a proposé.
À titre purement informatif, voici ces trois fichiers :
import { injectable } from '@theia/core/shared/inversify';
import {
DebugFrontendApplicationContribution
} from '@theia/debug/lib/browser/debug-frontend-application-contribution';
import {
OutlineViewContribution
} from '@theia/outline-view/lib/browser/outline-view-contribution';
import {
UrlLinkProvider
} from '@theia/terminal/lib/browser/terminal-url-link-provider';
import {
ViewContainer
} from '@theia/core/lib/browser';
import {
FILE_NAVIGATOR_ID
} from '@theia/navigator/lib/browser/navigator-widget';
import {
EXPLORER_VIEW_CONTAINER_ID,
EXPLORER_VIEW_CONTAINER_TITLE_OPTIONS,
NavigatorWidgetFactory
} from '@theia/navigator/lib/browser/navigator-widget-factory';
/**
* Neutralise seulement le layout par defaut du debogueur. Les commandes et les
* services restent charges pour les extensions qui en dependent.
*/
@injectable()
export class HiddenDebugContribution
extends DebugFrontendApplicationContribution {
override async initializeLayout(): Promise<void> {
// Conserve les fonctions de débogage, mais n'affiche pas leur vue.
}
}
/**
* Laisse ProductUiContribution placer Outline dans Explorer au bon moment, une
* fois Timeline disponible.
*/
@injectable()
export class ExplorerOutlineContribution
extends OutlineViewContribution {
override async initializeLayout(): Promise<void> {
// La vue sera insérée dans Explorer par ProductUiContribution.
}
}
/**
* Limite la detection des liens de terminal aux URL HTTP(S) explicites et
* evite d'inclure les separateurs courants de shell en fin de lien.
*/
@injectable()
export class FixedUrlLinkProvider extends UrlLinkProvider {
protected override readonly urlRegExp =
/https?:\/\/(?:(?:\d{1,3}\.){3}\d{1,3}|\[[0-9a-fA-F:]+\]|(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63})(?::\d{1,5})?(?:[/?#][^\s<>"']*)?/g;
}
/**
* Reconstruit Explorer avec l'arbre de fichiers seul. Les sections ajoutees par
* d'autres contributions sont ensuite masquees ou replacees explicitement.
*/
@injectable()
export class ProductNavigatorWidgetFactory
extends NavigatorWidgetFactory {
override async createWidget(): Promise<ViewContainer> {
const viewContainer = this.viewContainerFactory({
id: EXPLORER_VIEW_CONTAINER_ID,
progressLocationId: 'explorer'
});
viewContainer.setTitleOptions(
EXPLORER_VIEW_CONTAINER_TITLE_OPTIONS
);
const navigatorWidget =
await this.widgetManager.getOrCreateWidget(
FILE_NAVIGATOR_ID
);
viewContainer.addWidget(
navigatorWidget,
this.fileNavigatorWidgetOptions
);
return viewContainer;
}
}
import {
inject,
injectable
} from '@theia/core/shared/inversify';
import {
ApplicationShell,
FrontendApplication,
FrontendApplicationContribution,
ViewContainer,
WidgetManager
} from '@theia/core/lib/browser';
import {
EXPLORER_VIEW_CONTAINER_ID
} from '@theia/navigator/lib/browser/navigator-widget-factory';
import {
OUTLINE_WIDGET_FACTORY_ID
} from '@theia/outline-view/lib/browser/outline-view-contribution';
import {
TimelineService
} from '@theia/timeline/lib/browser/timeline-service';
import {
TimelineWidget
} from '@theia/timeline/lib/browser/timeline-widget';
import {
DebugWidget
} from '@theia/debug/lib/browser/view/debug-widget';
import {
TerminalService
} from '@theia/terminal/lib/browser/base/terminal-service';
import type {
TerminalWidget
} from '@theia/terminal/lib/browser/base/terminal-widget';
import {
FileNavigatorContribution
} from '@theia/navigator/lib/browser/navigator-contribution';
import {
TEST_VIEW_CONTAINER_ID
} from '@theia/test/lib/browser/view/test-view-contribution';
import {
FrontendApplicationStateService
} from '@theia/core/lib/browser/frontend-application-state';
import {
OpenEditorsWidget
} from '@theia/navigator/lib/browser/open-editors-widget/navigator-open-editors-widget';
const OUTLINE_PLACEMENT_DELAY = 0;
const MIN_BOTTOM_TERMINALS = 2;
const BOTTOM_PANEL_HEIGHT = 300;
/**
* Applique la forme produit du workbench apres les contributions Theia:
* Explorer a gauche, deux terminaux en bas, et Outline replie dans Explorer
* au lieu d'etre une vue separee.
*/
@injectable()
export class ProductUiContribution
implements FrontendApplicationContribution {
@inject(ApplicationShell)
protected readonly shell!: ApplicationShell;
@inject(WidgetManager)
protected readonly widgetManager!: WidgetManager;
@inject(TimelineService)
protected readonly timelineService!: TimelineService;
@inject(TerminalService)
protected readonly terminalService!: TerminalService;
@inject(FileNavigatorContribution)
protected readonly fileNavigatorContribution!: FileNavigatorContribution;
@inject(FrontendApplicationStateService)
protected readonly stateService!: FrontendApplicationStateService;
protected outlinePlaced = false;
onStart(_app: FrontendApplication): void {
this.keepOutlineAfterTimeline();
}
async onDidInitializeLayout(
_app: FrontendApplication
): Promise<void> {
await this.showExplorerSidebar();
await this.hideOpenEditorsSection();
await this.closeViewsHiddenByProductUi();
await this.placeOutlineInExplorer();
const firstTerminal = await this.ensureBottomTerminals();
await this.showBottomPanel(firstTerminal);
await this.showExplorerSidebar();
}
protected keepOutlineAfterTimeline(): void {
// Timeline est contribuee tardivement; Outline ne peut bouger qu'apres.
this.timelineService.onDidChangeProviders(
() => this.scheduleOutlinePlacement()
);
if (this.hasTimelineProvider()) {
this.scheduleOutlinePlacement();
}
}
protected scheduleOutlinePlacement(): void {
window.setTimeout(
() => void this.placeOutlineInExplorer(),
OUTLINE_PLACEMENT_DELAY
);
}
protected hasTimelineProvider(): boolean {
return this.timelineService.getSources().length > 0;
}
protected async showExplorerSidebar(): Promise<void> {
await this.fileNavigatorContribution.openView({
activate: false,
reveal: true
});
this.shell.expandPanel('left');
}
protected async getExplorerContainer(): Promise<
ViewContainer | undefined
> {
const explorer = await this.widgetManager.getOrCreateWidget(
EXPLORER_VIEW_CONTAINER_ID
);
if (explorer instanceof ViewContainer) {
return explorer;
}
return undefined;
}
protected async hideOpenEditorsSection(): Promise<void> {
const explorer = await this.getExplorerContainer();
const openEditors = await this.widgetManager.getOrCreateWidget(
OpenEditorsWidget.ID
);
explorer?.getPartFor(openEditors)?.setHidden(true);
}
protected async closeViewsHiddenByProductUi(): Promise<void> {
// Garde les fonctions disponibles, mais retire leurs vues par defaut.
await this.shell.closeWidget(TEST_VIEW_CONTAINER_ID);
await this.shell.closeWidget(DebugWidget.ID);
await this.closeDetachedOutline();
}
protected async closeDetachedOutline(): Promise<void> {
const outline = await this.widgetManager.getWidget(
OUTLINE_WIDGET_FACTORY_ID
);
if (outline && this.shell.getAreaFor(outline)) {
await this.shell.closeWidget(
OUTLINE_WIDGET_FACTORY_ID
);
}
}
protected async placeOutlineInExplorer(): Promise<void> {
// Outline est place apres Timeline pour conserver l'ordre voulu.
if (
this.outlinePlaced ||
!this.hasTimelineProvider()
) {
return;
}
const explorer = await this.getExplorerContainer();
const timeline = await this.widgetManager.getWidget(
TimelineWidget.ID
);
if (
!explorer ||
!timeline ||
!explorer.getPartFor(timeline)
) {
return;
}
const outline = await this.widgetManager.getOrCreateWidget(
OUTLINE_WIDGET_FACTORY_ID
);
if (!explorer.getPartFor(outline)) {
explorer.addWidget(outline, {
initiallyCollapsed: true,
canHide: true
});
}
this.outlinePlaced = true;
}
protected async ensureBottomTerminals(): Promise<TerminalWidget> {
const terminals = this.terminalService.all.filter(
terminal => this.shell.getAreaFor(terminal) === 'bottom'
);
let first = terminals[0];
if (!first) {
first = await this.createBottomTerminal();
}
if (terminals.length < MIN_BOTTOM_TERMINALS) {
await this.createSplitTerminal(first);
}
return first;
}
protected async createBottomTerminal(): Promise<TerminalWidget> {
const terminal = await this.terminalService.newTerminal({});
await terminal.start();
await this.shell.addWidget(terminal, {
area: 'bottom'
});
return terminal;
}
protected async createSplitTerminal(
first: TerminalWidget
): Promise<TerminalWidget> {
const terminal = await this.terminalService.newTerminal({});
await terminal.start();
await this.shell.addWidget(terminal, {
area: 'bottom',
ref: first,
mode: 'split-right'
});
return terminal;
}
protected async showBottomPanel(
firstTerminal: TerminalWidget
): Promise<void> {
this.shell.resize(BOTTOM_PANEL_HEIGHT, 'bottom');
this.shell.expandPanel('bottom');
await this.shell.pendingUpdates;
/*
* Ne pas attendre ici : Theia atteint l'etat "ready" seulement
* apres la fin de onDidInitializeLayout().
*/
void this.stateService.reachedState('ready').then(() => {
if (!firstTerminal.isDisposed) {
this.shell.expandPanel('bottom');
this.shell.bottomPanel.activateWidget(firstTerminal);
}
});
}
}
import { ContainerModule } from "@theia/core/shared/inversify";
import { FrontendApplicationContribution } from "@theia/core/lib/browser";
import { DebugFrontendApplicationContribution } from "@theia/debug/lib/browser/debug-frontend-application-contribution";
import { OutlineViewContribution } from "@theia/outline-view/lib/browser/outline-view-contribution";
import { UrlLinkProvider } from "@theia/terminal/lib/browser/terminal-url-link-provider";
import { NavigatorWidgetFactory } from "@theia/navigator/lib/browser/navigator-widget-factory";
import {
ExplorerOutlineContribution,
FixedUrlLinkProvider,
HiddenDebugContribution,
ProductNavigatorWidgetFactory,
} from "./custom-contributions";
import { ProductUiContribution } from "./product-ui-contribution";
export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
/*
* Ces contributions gardent les services Theia actifs, mais reprennent la
* main sur leur placement visible dans le workbench.
*/
rebind(OutlineViewContribution)
.to(ExplorerOutlineContribution)
.inSingletonScope();
rebind(DebugFrontendApplicationContribution)
.to(HiddenDebugContribution)
.inSingletonScope();
// Explorer est reconstruit sans la section "Open Editors".
rebind(NavigatorWidgetFactory)
.to(ProductNavigatorWidgetFactory)
.inSingletonScope();
// Le fournisseur standard detecte trop largement certains fragments.
rebind(UrlLinkProvider).to(FixedUrlLinkProvider).inSingletonScope();
// Orchestration finale du layout apres les contributions remplacees.
bind(ProductUiContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(ProductUiContribution);
});
Comme vous le voyez, le code est assez propre, même si la documentation est insuffisante – en plus d’être dépourvue d’accents. J’insiste sur un point : ce code couvre mes préférences personnelles, ce qui inclue des ajustements d’interface réalisés au moment de la compilation. Tout ce qui est fait dans ces trois fichiers pourrait être fait «à la main» dans l’application. Ces fichiers ne sont donc pas indispensables pour profiter de ce que Theia peut offrir.
Conclusion
Je suis enfin conquis par Theia, et n’en déplaise à ses détracteurs, l’aide que m’a apporté ChatGPT a été salvatrice. J’aurai été incapable, sinon en y passant plus de temps, d’accomplir ce qui représente finalement assez peu. Mais quand je mets dans l’équation le temps que m’a fait perdre OpenVSCode Server ces derniers jours, je me dis que j’y ai bien gagné au change.
L’impact sur la mémoire et le CPU se fait ressentir immédiatement, en particulier sur mes Raspberry Pi 4 2G : la page se charge en une fraction du temps qu’il fallait à OpenVSCode Server (pourtant bien optimisé outre mesure), tout est beaucoup plus réactif, le système s’aère.
Au-delà de l’aspect technique, il y a un aspect plus métaphysique difficilement descriptible : je me sens chez moi. J’ai l’impression de reprendre possession d’un outil que j’utilise partout, tout le temps, douze heures par jour ou plus.
Et le meilleur est encore à venir : j’ai à peine gratté la surface de ce que Theia me permet de faire. Les services qu’il va me rendre à l’avenir dépassent ce qu’un produit généraliste comme VSCode et ses dérivés peut m’apporter. Sa versatilité, sa sophistication, sa simplicité de prise en main dépassent mes attentes et rendent mes précédentes expériences infructueuses obsolètes.
Theia est mon nouvel atout développement et je ne suis pas prêt de le quitter.
