diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml
index 0c2b20ff3..0ba0dc4f4 100644
--- a/bin/assets/i18n/de.xml
+++ b/bin/assets/i18n/de.xml
@@ -47,7 +47,6 @@ Diese Operation kann nicht rückgängig gemacht werden!
Autor
Klammern & Tags automatisch vervollständigen
XML-Tags automatisch vervollständigen
- Klammern & Tags automatisch vervollständigen
Einzugsart- & weite automatisch erkennen
Auto-Auffrischen bei Dateiänderung im Festspeicher
Backticks ``
@@ -267,6 +266,8 @@ Soll es jetzt heruntergeladen werden?
Bearbeiten
Build bearbeiten
Angewählten individuellen Output-Parser bearbeiten
+ JSON-Einstellungen bearbeiten
+ Vollständige Plugin-Konfigurationsdatei öffnen.
Editor-Schriftart und -größe...
Editor-Schriftfunktionen
UI-Schriftfunktionen
@@ -279,7 +280,6 @@ Soll es jetzt heruntergeladen werden?
Optionale Ligaturen (dlig)
Optionale dekorative oder stilistische Ligaturen der Schriftart.
Editor-Schriftgröße
- Editor-Schriftfunktionen
Editor: Schritt zurück
Editor: Schritt nach vorn
Der Dateiname darf nicht leer sein.
@@ -486,6 +486,22 @@ Beachten Sie, dass der Großteil der Tastenzuweisungen hier definiert wird.LSP: Speichernutzung
LSP: Plugin-Neustart
LSP: Semantische Markierungen aktualisieren
+ Breadcrumb-Navigation
+ Sprachbezogene Breadcrumb-Navigation über dem Editor anzeigen.
+ Breadcrumb-Höhe
+ Höhe der Editor-Breadcrumb-Navigation als CSS-Längenangabe.
+ Semantische Hervorhebung für Sprachen deaktivieren
+ Kommagetrennte Sprachkennungen, für die semantische Hervorhebung deaktiviert ist.
+ Hover-Verzögerung
+ Wartezeit vor der Anzeige von Sprachserver-Informationen beim Darüberfahren.
+ Semantische Hervorhebung
+ Semantische Tokens von Sprachservern verwenden, wenn verfügbar.
+ Server nach Leerlauf schließen
+ Zeit, die ein ungenutzter Sprachserver vor dem Schließen weiterläuft.
+ Sprachserver-Protokolle unterdrücken
+ Unkritische Sprachserver-Protokollmeldungen ausblenden.
+ Sprachserver-Protokolle kürzen
+ Jede Sprachserver-Protokollzeile auf 1 KiB begrenzen.
LSP: Symbol unter Eingabemarke umbenennen
LSP-Client neu starten
LSP: Dokumentsymbole anzeigen
@@ -607,6 +623,7 @@ in einem neuen Fenster geöffnet werden.
Widget wählen
Bitte Datei-URL eingeben...
Erweiterungsverwaltung
+ Plugins
Erweiterungsverwaltung öffnen
Beschreibung
Aktiviert
@@ -843,7 +860,6 @@ in der Baumansicht und in Öffen-/Schließdialogen aktivieren.
Zu findender Typ
Schriftart und -größe der Oberfläche...
Schriftgröße der Oberfläche
- UI-Schriftfunktionen
Sprache der Oberfläche
Multisample-Antialiasing-Stufe
Schriftgröße im Paneel der Oberfläche
@@ -1105,4 +1121,93 @@ Für sichtbare Änderung ecode neu starten.
Synchronisiert die Darstellung mit der Bildwiederholrate. Neustart erforderlich.
Vor dem Schließen des Tabs warnen
Fragt vor dem Schließen eines Terminals mit laufendem Programm nach.
+ Anthropic API Key
+ DeepSeek API Key
+ Credential used to access this model provider.
+ GitHub Models API Key
+ Google AI API Key
+ Mistral API Key
+ Moonshot AI API Key
+ NVIDIA API Key
+ OpenAI API Key
+ OpenRouter API Key
+ Perplexity API Key
+ Together AI API Key
+ xAI API Key
+ Xiaomi MiMo API Key
+ API Keys
+ Display Reasoning
+ Display reasoning content returned by supported models.
+ Enable Model Catalog
+ Download model metadata from the configured catalog.
+ Model Catalog Refresh Interval
+ Hours between model catalog refreshes.
+ Model Catalog URL
+ URL used to download model catalog metadata.
+ Load VS Code Snippets
+ Load compatible snippets installed for VS Code.
+ Maximum Label Characters
+ Maximum number of characters shown in a suggestion label.
+ Maximum Signature Help Width
+ Maximum signature help popup width as a CSS length.
+ Maximum Suggestion Documentation Width
+ Maximum documentation popup width as a CSS length.
+ Signature Help Documentation
+ Show documentation alongside signature help.
+ Multiline Signature Help
+ Allow signature help to use multiple lines.
+ Suggestion Documentation
+ Show documentation alongside completion suggestions.
+ Syntax Highlight Suggestions
+ Apply syntax highlighting to completion suggestions.
+ Fetch Global Variables
+ Request global variables while debugging.
+ Fetch Registers
+ Request processor registers while debugging.
+ Load VS Code Launch Configuration
+ Load compatible launch configurations from .vscode/launch.json.
+ Silent Debugger Logs
+ Hide non-critical debugger log messages.
+ Discord Application ID
+ Discord application identifier used for Rich Presence.
+ Git Integration
+ Include repository information in Rich Presence.
+ Language Icon Bindings
+ JSON object mapping language identifiers to Discord image assets.
+ Language Icons
+ Display an icon for the active document language.
+ Format on Save
+ Automatically format supported documents when saving.
+ Highlight File Tree Changes
+ Highlight files with Git changes in the file tree.
+ File Tree Highlight Color
+ CSS color used to highlight changed files.
+ Silent Git Logs
+ Hide non-critical Git plugin log messages.
+ Recurse into Submodules
+ Include submodule changes when collecting repository status.
+ Show Branch in Status Bar
+ Display the current Git branch in the status bar.
+ Show Modifications in Status Bar
+ Display repository modification counts in the status bar.
+ Refresh Frequency
+ How often Git information in the interface is refreshed.
+ Lint Delay
+ Time to wait before linting after an edit.
+ Disable Linter Languages
+ Comma-separated language identifiers where linters are disabled.
+ Disable LSP Diagnostic Languages
+ Comma-separated language identifiers where LSP diagnostics are disabled.
+ Error Lens
+ Display diagnostic messages inline in the editor.
+ Language Server Diagnostics
+ Display diagnostics reported by language servers.
+ Ignore Warnings When Navigating
+ Skip warnings when navigating between diagnostics.
+ Spell Check Delay
+ Time to wait before checking spelling after an edit.
+ Edit Matching Tags
+ Update the matching tag automatically while editing.
+ Highlight Matching Tags
+ Highlight the matching XML or HTML tag at the cursor.
diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml
index a738d4bf3..c09492c19 100644
--- a/bin/assets/i18n/en.xml
+++ b/bin/assets/i18n/en.xml
@@ -31,7 +31,6 @@ This operation cannot be reverted!
Author
Auto-Close Brackets & Tags
Auto Close XML Tags
- Auto-Close Brackets & Tags
Auto Detect Indent Type & Width
Auto-Reload on Disk Change
Back Quotes ``
@@ -251,6 +250,8 @@ Do you want to download it now?
Edit
Edit Build
Edit Selected Custom Output Parser
+ Edit Settings JSON
+ Open the complete plugin configuration file.
Editor Font & Size...
Editor Font Features
UI Font Features
@@ -263,7 +264,6 @@ Do you want to download it now?
Discretionary Ligatures (dlig)
Optional decorative or stylistic ligatures provided by the font.
Editor Font Size
- Editor Font Features
Editor Go Back
Editor Go Forward
You must set a name to the file.
@@ -470,6 +470,22 @@ Be aware that many of the core keybindings can be found there.
Lsp Memory Usage
Lsp Plugin Restart
Lsp Refresh Semantic Highlighting
+ Breadcrumb Navigation
+ Display language-aware breadcrumb navigation above the editor.
+ Breadcrumb Height
+ Height of the editor breadcrumb using a CSS length.
+ Disable Semantic Highlighting Languages
+ Comma-separated language identifiers where semantic highlighting is disabled.
+ Hover Delay
+ Time to wait before showing language server information while hovering.
+ Semantic Highlighting
+ Use semantic tokens provided by language servers when available.
+ Server Close After Idle Time
+ Time to keep an unused language server running before closing it.
+ Silent Language Server Logs
+ Hide non-critical language server log messages.
+ Trim Language Server Logs
+ Limit each language server log line to 1 KiB.
Lsp Rename Symbol Under Cursor
Restart LSP Client
Lsp Show Document Symbols
@@ -591,6 +607,7 @@ controls whether a new window is created or not.
Pick Widget
Please enter the file URL...
Plugins Manager
+ Plugins
Plugin Manager Open
Description
Enabled
@@ -828,7 +845,6 @@ the directory tree and in file dialogs to open a folder or file.
Type to Locate
UI Font & Size...
Ui Font Size
- UI Font Features
UI Language
Multisample Anti-Aliasing Level
Ui Panel Font Size
@@ -1090,4 +1106,93 @@ Restart ecode to see the changes.
Synchronize rendering with the display refresh rate. Restart required.
Warn Before Closing Tab
Ask before closing a terminal while a program is running.
+ Anthropic API Key
+ DeepSeek API Key
+ Credential used to access this model provider.
+ GitHub Models API Key
+ Google AI API Key
+ Mistral API Key
+ Moonshot AI API Key
+ NVIDIA API Key
+ OpenAI API Key
+ OpenRouter API Key
+ Perplexity API Key
+ Together AI API Key
+ xAI API Key
+ Xiaomi MiMo API Key
+ API Keys
+ Display Reasoning
+ Display reasoning content returned by supported models.
+ Enable Model Catalog
+ Download model metadata from the configured catalog.
+ Model Catalog Refresh Interval
+ Hours between model catalog refreshes.
+ Model Catalog URL
+ URL used to download model catalog metadata.
+ Load VS Code Snippets
+ Load compatible snippets installed for VS Code.
+ Maximum Label Characters
+ Maximum number of characters shown in a suggestion label.
+ Maximum Signature Help Width
+ Maximum signature help popup width as a CSS length.
+ Maximum Suggestion Documentation Width
+ Maximum documentation popup width as a CSS length.
+ Signature Help Documentation
+ Show documentation alongside signature help.
+ Multiline Signature Help
+ Allow signature help to use multiple lines.
+ Suggestion Documentation
+ Show documentation alongside completion suggestions.
+ Syntax Highlight Suggestions
+ Apply syntax highlighting to completion suggestions.
+ Fetch Global Variables
+ Request global variables while debugging.
+ Fetch Registers
+ Request processor registers while debugging.
+ Load VS Code Launch Configuration
+ Load compatible launch configurations from .vscode/launch.json.
+ Silent Debugger Logs
+ Hide non-critical debugger log messages.
+ Discord Application ID
+ Discord application identifier used for Rich Presence.
+ Git Integration
+ Include repository information in Rich Presence.
+ Language Icon Bindings
+ JSON object mapping language identifiers to Discord image assets.
+ Language Icons
+ Display an icon for the active document language.
+ Format on Save
+ Automatically format supported documents when saving.
+ Highlight File Tree Changes
+ Highlight files with Git changes in the file tree.
+ File Tree Highlight Color
+ CSS color used to highlight changed files.
+ Silent Git Logs
+ Hide non-critical Git plugin log messages.
+ Recurse into Submodules
+ Include submodule changes when collecting repository status.
+ Show Branch in Status Bar
+ Display the current Git branch in the status bar.
+ Show Modifications in Status Bar
+ Display repository modification counts in the status bar.
+ Refresh Frequency
+ How often Git information in the interface is refreshed.
+ Lint Delay
+ Time to wait before linting after an edit.
+ Disable Linter Languages
+ Comma-separated language identifiers where linters are disabled.
+ Disable LSP Diagnostic Languages
+ Comma-separated language identifiers where LSP diagnostics are disabled.
+ Error Lens
+ Display diagnostic messages inline in the editor.
+ Language Server Diagnostics
+ Display diagnostics reported by language servers.
+ Ignore Warnings When Navigating
+ Skip warnings when navigating between diagnostics.
+ Spell Check Delay
+ Time to wait before checking spelling after an edit.
+ Edit Matching Tags
+ Update the matching tag automatically while editing.
+ Highlight Matching Tags
+ Highlight the matching XML or HTML tag at the cursor.
diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml
index 538fe4971..361ec2ee8 100644
--- a/bin/assets/i18n/fr.xml
+++ b/bin/assets/i18n/fr.xml
@@ -32,7 +32,6 @@ Cette opération est irréversible !
Auteur
Fermeture automatique des crochets et des balises
Fermeture automatique des balises XML
- Fermeture automatique des crochets et des balises
Détection automatique du type et de la taille de l'indentation
Rechargement automatique en cas de modification du disque
Guillemets inversés ``
@@ -252,6 +251,8 @@ Voulez-vous le télécharger maintenant ?
Modifier
Modifier la construction
Modifier l'analyseur de sortie personnalisé sélectionné
+ Modifier les paramètres JSON
+ Ouvrir le fichier de configuration complet du plugin.
Police et taille de l'éditeur...
Fonctionnalités de la police de l'éditeur
Fonctionnalités de la police de l'interface
@@ -264,7 +265,6 @@ Voulez-vous le télécharger maintenant ?
Ligatures discrétionnaires (dlig)
Ligatures décoratives ou stylistiques facultatives fournies par la police.
Taille de la police de l'éditeur
- Fonctionnalités de la police de l'éditeur
Éditeur : retour en arrière
Éditeur : avancer
Vous devez définir un nom pour le fichier.
@@ -469,6 +469,22 @@ La mémoire de discussion ne pourra être supprimée manuellement que dans l'his
LSP : utilisation de la mémoire
LSP : redémarrer le plugin
LSP : actualiser la mise en évidence sémantique
+ Navigation par fil d’Ariane
+ Afficher la navigation contextuelle par fil d’Ariane au-dessus de l’éditeur.
+ Hauteur du fil d’Ariane
+ Hauteur du fil d’Ariane de l’éditeur exprimée comme une longueur CSS.
+ Désactiver la coloration sémantique pour certaines langues
+ Identifiants de langues séparés par des virgules pour lesquels la coloration sémantique est désactivée.
+ Délai de survol
+ Temps d’attente avant d’afficher les informations du serveur de langage lors du survol.
+ Coloration sémantique
+ Utiliser les jetons sémantiques fournis par les serveurs de langage lorsqu’ils sont disponibles.
+ Fermeture du serveur après inactivité
+ Durée pendant laquelle un serveur de langage inutilisé reste actif avant sa fermeture.
+ Masquer les journaux des serveurs de langage
+ Masquer les messages non critiques des serveurs de langage.
+ Tronquer les journaux des serveurs de langage
+ Limiter chaque ligne de journal d’un serveur de langage à 1 Kio.
LSP : renommer le symbole sous le curseur
LSP : redémarrer le client
LSP : afficher les symboles du document
@@ -590,6 +606,7 @@ détermine si une nouvelle fenêtre est créée ou non.
Choisir une widget
Veuillez saisir l'URL du fichier...
Gestionnaire de plugins
+ Plugins
Gestionnaire de plugins ouvert
Description
Activé
@@ -822,7 +839,6 @@ dans l'arborescence de répertoires ainsi que dans les boites de dialogues de s
Type à localiser
Police et taille de l'interface utilisateur...
Interface utilisateur : taille de la police
- Fonctionnalités de la police de l'interface
Interface utilisateur : langue
Interface utilisateur : niveau d'anti-aliasing multiéchantillon
Interface utilisateur : taille de police des panneaux
@@ -1089,4 +1105,93 @@ Redémarrer ecode pour voir les changements.
Synchronise le rendu avec la fréquence de l'écran. Redémarrage requis.
Avertir avant de fermer l'onglet
Demande confirmation avant de fermer un terminal exécutant un programme.
+ Anthropic API Key
+ DeepSeek API Key
+ Credential used to access this model provider.
+ GitHub Models API Key
+ Google AI API Key
+ Mistral API Key
+ Moonshot AI API Key
+ NVIDIA API Key
+ OpenAI API Key
+ OpenRouter API Key
+ Perplexity API Key
+ Together AI API Key
+ xAI API Key
+ Xiaomi MiMo API Key
+ API Keys
+ Display Reasoning
+ Display reasoning content returned by supported models.
+ Enable Model Catalog
+ Download model metadata from the configured catalog.
+ Model Catalog Refresh Interval
+ Hours between model catalog refreshes.
+ Model Catalog URL
+ URL used to download model catalog metadata.
+ Load VS Code Snippets
+ Load compatible snippets installed for VS Code.
+ Maximum Label Characters
+ Maximum number of characters shown in a suggestion label.
+ Maximum Signature Help Width
+ Maximum signature help popup width as a CSS length.
+ Maximum Suggestion Documentation Width
+ Maximum documentation popup width as a CSS length.
+ Signature Help Documentation
+ Show documentation alongside signature help.
+ Multiline Signature Help
+ Allow signature help to use multiple lines.
+ Suggestion Documentation
+ Show documentation alongside completion suggestions.
+ Syntax Highlight Suggestions
+ Apply syntax highlighting to completion suggestions.
+ Fetch Global Variables
+ Request global variables while debugging.
+ Fetch Registers
+ Request processor registers while debugging.
+ Load VS Code Launch Configuration
+ Load compatible launch configurations from .vscode/launch.json.
+ Silent Debugger Logs
+ Hide non-critical debugger log messages.
+ Discord Application ID
+ Discord application identifier used for Rich Presence.
+ Git Integration
+ Include repository information in Rich Presence.
+ Language Icon Bindings
+ JSON object mapping language identifiers to Discord image assets.
+ Language Icons
+ Display an icon for the active document language.
+ Format on Save
+ Automatically format supported documents when saving.
+ Highlight File Tree Changes
+ Highlight files with Git changes in the file tree.
+ File Tree Highlight Color
+ CSS color used to highlight changed files.
+ Silent Git Logs
+ Hide non-critical Git plugin log messages.
+ Recurse into Submodules
+ Include submodule changes when collecting repository status.
+ Show Branch in Status Bar
+ Display the current Git branch in the status bar.
+ Show Modifications in Status Bar
+ Display repository modification counts in the status bar.
+ Refresh Frequency
+ How often Git information in the interface is refreshed.
+ Lint Delay
+ Time to wait before linting after an edit.
+ Disable Linter Languages
+ Comma-separated language identifiers where linters are disabled.
+ Disable LSP Diagnostic Languages
+ Comma-separated language identifiers where LSP diagnostics are disabled.
+ Error Lens
+ Display diagnostic messages inline in the editor.
+ Language Server Diagnostics
+ Display diagnostics reported by language servers.
+ Ignore Warnings When Navigating
+ Skip warnings when navigating between diagnostics.
+ Spell Check Delay
+ Time to wait before checking spelling after an edit.
+ Edit Matching Tags
+ Update the matching tag automatically while editing.
+ Highlight Matching Tags
+ Highlight the matching XML or HTML tag at the cursor.
diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml
index d6a1369d0..83ed008c4 100644
--- a/bin/assets/i18n/zh.xml
+++ b/bin/assets/i18n/zh.xml
@@ -31,7 +31,6 @@
参数
关闭标签页?
作者
- Auto-Close Brackets & Tags
自动关闭XML标签
自动闭合括号和标签
Auto Detect Indent Type & Width
@@ -186,6 +185,8 @@
编辑
编辑构建
编辑已选的自定义输出解析器
+ 编辑设置 JSON
+ 打开完整的插件配置文件。
编辑器字体和大小...
编辑器字体特性
界面字体特性
@@ -198,7 +199,6 @@
任意连字 (dlig)
字体提供的可选装饰性或风格化连字。
编辑器字体大小
- 编辑器字体特性
编辑器撤销
编辑器重做
开启颜色选择器
@@ -371,6 +371,22 @@ Name your stash (optional):
Lsp Memory Usage
Lsp Plugin Restart
Lsp Refresh Semantic Highlighting
+ 面包屑导航
+ 在编辑器上方显示语言感知的面包屑导航。
+ 面包屑高度
+ 使用 CSS 长度设置编辑器面包屑的高度。
+ 禁用语义高亮的语言
+ 以逗号分隔要禁用语义高亮的语言标识符。
+ 悬停延迟
+ 悬停时显示语言服务器信息前的等待时间。
+ 语义高亮
+ 在可用时使用语言服务器提供的语义标记。
+ 服务器空闲关闭时间
+ 关闭未使用的语言服务器前保持其运行的时间。
+ 隐藏语言服务器日志
+ 隐藏非关键的语言服务器日志消息。
+ 截断语言服务器日志
+ 将每行语言服务器日志限制为 1 KiB。
Lsp Rename Symbol Under Cursor
Restart LSP Client
Lsp Switch Header Source
@@ -462,6 +478,7 @@ Restart ecode to see the changes.
选择组件
请输入文件URL...
插件管理器
+ 插件
打开插件管理器
描述
已开启
@@ -622,7 +639,6 @@ file in the directory tree.
Type to Locate
界面字体和大小...
Ui字体大小
- 界面字体特性
界面语言
多重采样抗锯齿级别
界面面板字体大小
@@ -894,4 +910,93 @@ file in the directory tree.
按字符换行
换行依据
按单词换行
+ Anthropic API Key
+ DeepSeek API Key
+ Credential used to access this model provider.
+ GitHub Models API Key
+ Google AI API Key
+ Mistral API Key
+ Moonshot AI API Key
+ NVIDIA API Key
+ OpenAI API Key
+ OpenRouter API Key
+ Perplexity API Key
+ Together AI API Key
+ xAI API Key
+ Xiaomi MiMo API Key
+ API Keys
+ Display Reasoning
+ Display reasoning content returned by supported models.
+ Enable Model Catalog
+ Download model metadata from the configured catalog.
+ Model Catalog Refresh Interval
+ Hours between model catalog refreshes.
+ Model Catalog URL
+ URL used to download model catalog metadata.
+ Load VS Code Snippets
+ Load compatible snippets installed for VS Code.
+ Maximum Label Characters
+ Maximum number of characters shown in a suggestion label.
+ Maximum Signature Help Width
+ Maximum signature help popup width as a CSS length.
+ Maximum Suggestion Documentation Width
+ Maximum documentation popup width as a CSS length.
+ Signature Help Documentation
+ Show documentation alongside signature help.
+ Multiline Signature Help
+ Allow signature help to use multiple lines.
+ Suggestion Documentation
+ Show documentation alongside completion suggestions.
+ Syntax Highlight Suggestions
+ Apply syntax highlighting to completion suggestions.
+ Fetch Global Variables
+ Request global variables while debugging.
+ Fetch Registers
+ Request processor registers while debugging.
+ Load VS Code Launch Configuration
+ Load compatible launch configurations from .vscode/launch.json.
+ Silent Debugger Logs
+ Hide non-critical debugger log messages.
+ Discord Application ID
+ Discord application identifier used for Rich Presence.
+ Git Integration
+ Include repository information in Rich Presence.
+ Language Icon Bindings
+ JSON object mapping language identifiers to Discord image assets.
+ Language Icons
+ Display an icon for the active document language.
+ Format on Save
+ Automatically format supported documents when saving.
+ Highlight File Tree Changes
+ Highlight files with Git changes in the file tree.
+ File Tree Highlight Color
+ CSS color used to highlight changed files.
+ Silent Git Logs
+ Hide non-critical Git plugin log messages.
+ Recurse into Submodules
+ Include submodule changes when collecting repository status.
+ Show Branch in Status Bar
+ Display the current Git branch in the status bar.
+ Show Modifications in Status Bar
+ Display repository modification counts in the status bar.
+ Refresh Frequency
+ How often Git information in the interface is refreshed.
+ Lint Delay
+ Time to wait before linting after an edit.
+ Disable Linter Languages
+ Comma-separated language identifiers where linters are disabled.
+ Disable LSP Diagnostic Languages
+ Comma-separated language identifiers where LSP diagnostics are disabled.
+ Error Lens
+ Display diagnostic messages inline in the editor.
+ Language Server Diagnostics
+ Display diagnostics reported by language servers.
+ Ignore Warnings When Navigating
+ Skip warnings when navigating between diagnostics.
+ Spell Check Delay
+ Time to wait before checking spelling after an edit.
+ Edit Matching Tags
+ Update the matching tag automatically while editing.
+ Highlight Matching Tags
+ Highlight the matching XML or HTML tag at the cursor.
diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp
index 23f40cb58..240637a1c 100644
--- a/include/eepp/ui/uicodeeditor.hpp
+++ b/include/eepp/ui/uicodeeditor.hpp
@@ -29,7 +29,6 @@ class UIDocFindReplace;
class UIIcon;
class UICodeEditor;
-class UIWindow;
class UIScrollBar;
class UILoader;
class UIPopUpMenu;
@@ -45,37 +44,57 @@ class UICodeEditorPlugin {
public:
typedef std::function OnReadyCb;
virtual std::string getId() = 0;
+
virtual std::string getTitle() = 0;
+
virtual std::string getDescription() = 0;
+
virtual bool isReady() const = 0;
- virtual bool hasGUIConfig() { return false; }
+
virtual bool hasFileConfig() { return false; }
- virtual UIWindow* getGUIConfig() { return nullptr; }
+
virtual std::string getFileConfigPath() { return ""; }
virtual ~UICodeEditorPlugin() {}
virtual void onRegister( UICodeEditor* ) = 0;
+
virtual void onUnregister( UICodeEditor* ) = 0;
+
virtual bool onKeyDown( UICodeEditor*, const KeyEvent& ) { return false; }
+
virtual bool onKeyUp( UICodeEditor*, const KeyEvent& ) { return false; }
+
virtual bool onTextInput( UICodeEditor*, const TextInputEvent& ) { return false; }
+
virtual void update( UICodeEditor* ) {}
+
virtual void preDraw( UICodeEditor*, const Vector2f& /*startScroll*/,
const Float& /*lineHeight*/, const TextPosition& /*cursor*/ ) {}
+
virtual void postDraw( UICodeEditor*, const Vector2f& /*startScroll*/,
const Float& /*lineHeight*/, const TextPosition& /*cursor*/ ) {}
+
virtual void onFocus( UICodeEditor* ) {}
+
virtual void onFocusLoss( UICodeEditor* ) {}
+
virtual bool onMouseDown( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; }
+
virtual bool onMouseMove( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; }
+
virtual bool onMouseUp( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; }
+
virtual bool onMouseClick( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; }
+
virtual bool onMouseDoubleClick( UICodeEditor*, const Vector2i&, const Uint32& ) {
return false;
}
+
virtual bool onMouseOver( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; }
+
virtual bool onMouseLeave( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; }
+
virtual bool onCreateContextMenu( UICodeEditor*, UIPopUpMenu* /*menu*/,
const Vector2i& /*position*/, const Uint32& /*flags*/ ) {
return false;
@@ -142,6 +161,7 @@ class EE_API DocEvent : public Event {
public:
DocEvent( Node* node, TextDocument* doc, const Uint32& eventType ) :
Event( node, eventType ), doc( doc ) {}
+
TextDocument* getDoc() const { return doc; }
protected:
@@ -165,7 +185,9 @@ class EE_API DocSyntaxDefEvent : public DocEvent {
DocSyntaxDefEvent( Node* node, TextDocument* doc, const Uint32& eventType,
const std::string& oldLang, const std::string& newLang ) :
DocEvent( node, doc, eventType ), oldLang( oldLang ), newLang( newLang ) {}
+
const std::string& getOldLang() const { return oldLang; }
+
const std::string& getNewLang() const { return newLang; }
protected:
diff --git a/src/eepp/ui/tools/uidocfindreplace.cpp b/src/eepp/ui/tools/uidocfindreplace.cpp
index 2ecc5aa2c..33f54ab12 100644
--- a/src/eepp/ui/tools/uidocfindreplace.cpp
+++ b/src/eepp/ui/tools/uidocfindreplace.cpp
@@ -102,7 +102,7 @@ const char DOC_FIND_REPLACE_CSS[] = R"css(
}
.ce_find_replace_box .input-find.error,
.ce_find_replace_box .input-replace.error {
- border-color: #ff4040;
+ border-color: var(--theme-error);
}
)css";
diff --git a/src/tools/ecode/applayout.xml.hpp b/src/tools/ecode/applayout.xml.hpp
index 10ca26b12..cef7ad4a6 100644
--- a/src/tools/ecode/applayout.xml.hpp
+++ b/src/tools/ecode/applayout.xml.hpp
@@ -82,7 +82,7 @@ StatusBar > #doc_info {
}
#search_find.error,
#search_replace.error {
- border-color: #ff4040;
+ border-color: var(--theme-error);
}
TableView#locate_bar_table > tableview::row > tableview::cell:nth-child(2),
TableView#locate_bar_table > tableview::row > tableview::cell:nth-child(3) {
diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp
index 1dd81cb47..1a23e63c0 100644
--- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp
+++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp
@@ -4,6 +4,7 @@
#include "../../appconfig.hpp"
#include "../../notificationcenter.hpp"
+#include "../../settingspage.hpp"
#include "../../widgetcommandexecuter.hpp"
#include
@@ -13,6 +14,66 @@ using json = nlohmann::json;
namespace ecode {
+void AIAssistantPlugin::registerSettings( SettingsPage& page ) {
+ auto general = i18n( "general", "General" );
+ page.addGroup( general );
+ page.addBool( "display-reasoning", "/config/display_reasoning",
+ i18n( "aiassistant_display_reasoning", "Display Reasoning" ),
+ i18n( "aiassistant_display_reasoning_desc",
+ "Display reasoning content returned by supported models." ),
+ false, general );
+ page.addBool( "model-catalog-enabled", "/config/model_catalog_enabled",
+ i18n( "aiassistant_model_catalog_enabled", "Enable Model Catalog" ),
+ i18n( "aiassistant_model_catalog_enabled_desc",
+ "Download model metadata from the configured catalog." ),
+ true, general );
+ page.addText(
+ "model-catalog-url", "/config/model_catalog_url",
+ i18n( "aiassistant_model_catalog_url", "Model Catalog URL" ),
+ i18n( "aiassistant_model_catalog_url_desc",
+ "URL used to download model catalog metadata." ),
+ "https://models.dev/api.json", []( const std::string& value ) { return !value.empty(); },
+ false, general );
+ page.addInteger(
+ "model-catalog-refresh-hours", "/config/model_catalog_refresh_hours",
+ i18n( "aiassistant_model_catalog_refresh_hours", "Model Catalog Refresh Interval" ),
+ i18n( "aiassistant_model_catalog_refresh_hours_desc",
+ "Hours between model catalog refreshes." ),
+ 1, 8760, 24, general );
+
+ auto apiKeys = i18n( "aiassistant_api_keys", "API Keys" );
+ page.addGroup( apiKeys );
+ struct Provider {
+ const char* id;
+ const char* name;
+ const char* legacyKey;
+ };
+ static constexpr Provider providers[] = {
+ { "openai", "OpenAI", "openai_api_key" },
+ { "anthropic", "Anthropic", "anthropic_api_key" },
+ { "google", "Google AI", "google_ai_api_key" },
+ { "deepseek", "DeepSeek", "deepseek_api_key" },
+ { "mistral", "Mistral", "mistral_api_key" },
+ { "xai", "xAI", "xai_api_key" },
+ { "github", "GitHub Models", "github_api_key" },
+ { "perplexity", "Perplexity", "perplexity_api_key" },
+ { "openrouter", "OpenRouter", "openrouter_api_key" },
+ { "moonshotai", "Moonshot AI", "moonshot_api_key" },
+ { "nvidia", "NVIDIA", "nvidia_api_key" },
+ { "togetherai", "Together AI", "together_api_key" },
+ { "xiaomi", "Xiaomi MiMo", "mimo_api_key" },
+ };
+ for ( const auto& provider : providers ) {
+ page.addSecret(
+ std::string{ "api-key-" } + provider.id,
+ std::string{ "/config/api_keys/" } + provider.id,
+ i18n( std::string{ "aiassistant_api_key_" } + provider.id,
+ String::fromUtf8( std::string_view{ provider.name } ) + " API Key" ),
+ i18n( "aiassistant_api_key_desc", "Credential used to access this model provider." ),
+ {}, apiKeys, std::string{ "/config/" } + provider.legacyKey );
+ }
+}
+
static std::initializer_list AIAssistantUnlockedCommandList = {
"new-ai-assistant"
@@ -285,7 +346,7 @@ void AIAssistantPlugin::load( PluginManager* pluginManager ) {
userConfig["model_catalog_url"].is_string() )
catalogSettings.url = userConfig["model_catalog_url"].get();
if ( userConfig.contains( "model_catalog_refresh_hours" ) &&
- userConfig["model_catalog_refresh_hours"].is_number_unsigned() )
+ userConfig["model_catalog_refresh_hours"].is_number_integer() )
catalogSettings.refreshIntervalHours =
userConfig["model_catalog_refresh_hours"].get();
}
diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp
index 3e9a2cd19..a66d2df2f 100644
--- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp
+++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp
@@ -40,7 +40,12 @@ class AIAssistantPlugin : public PluginBase {
std::string getDescription() override { return Definition().description; }
+ bool hasSettingsPage() const override { return true; }
+
+ void registerSettings( SettingsPage& page ) override;
+
const LLMProviders& getProviders() { return mProviders; }
+
const ACPAgents& getAgents() { return mAgents; }
std::string getPluginStatePath() const;
diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp
index 11b69a143..5d1ff1f39 100644
--- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp
+++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp
@@ -1,4 +1,5 @@
#include "autocompleteplugin.hpp"
+#include "../../settingspage.hpp"
#include "../../universallocator.hpp"
#include
#include
@@ -25,6 +26,55 @@ using namespace std::literals;
namespace ecode {
+void AutoCompletePlugin::registerSettings( SettingsPage& page ) {
+ page.addGroup( i18n( "general", "General" ) );
+ page.addBool(
+ "suggestions-syntax-highlight", "/config/suggestions_syntax_highlight",
+ i18n( "autocomplete_suggestions_syntax_highlight", "Syntax Highlight Suggestions" ),
+ i18n( "autocomplete_suggestions_syntax_highlight_desc",
+ "Apply syntax highlighting to completion suggestions." ),
+ true );
+ page.addInteger( "max-label-characters", "/config/max_label_characters",
+ i18n( "autocomplete_max_label_characters", "Maximum Label Characters" ),
+ i18n( "autocomplete_max_label_characters_desc",
+ "Maximum number of characters shown in a suggestion label." ),
+ 1, 10000, 100 );
+ auto cssLength = []( const std::string& text ) { return StyleSheetLength::isLength( text ); };
+ page.addText( "max-suggestion-documentation-width",
+ "/config/max_suggestion_documentation_width",
+ i18n( "autocomplete_max_suggestion_documentation_width",
+ "Maximum Suggestion Documentation Width" ),
+ i18n( "autocomplete_max_suggestion_documentation_width_desc",
+ "Maximum documentation popup width as a CSS length." ),
+ "100%", cssLength );
+ page.addText( "max-signature-helper-width", "/config/max_signature_helper_width",
+ i18n( "autocomplete_max_signature_helper_width", "Maximum Signature Help Width" ),
+ i18n( "autocomplete_max_signature_helper_width_desc",
+ "Maximum signature help popup width as a CSS length." ),
+ "90%", cssLength );
+ page.addBool( "signature-help-multi-line", "/config/signature_help_multi_line",
+ i18n( "autocomplete_signature_help_multi_line", "Multiline Signature Help" ),
+ i18n( "autocomplete_signature_help_multi_line_desc",
+ "Allow signature help to use multiple lines." ),
+ true );
+ page.addBool( "suggestion-documentation", "/config/suggestion_documentation",
+ i18n( "autocomplete_suggestion_documentation", "Suggestion Documentation" ),
+ i18n( "autocomplete_suggestion_documentation_desc",
+ "Show documentation alongside completion suggestions." ),
+ true );
+ page.addBool(
+ "signature-help-documentation", "/config/signature_help_documentation",
+ i18n( "autocomplete_signature_help_documentation", "Signature Help Documentation" ),
+ i18n( "autocomplete_signature_help_documentation_desc",
+ "Show documentation alongside signature help." ),
+ true );
+ page.addBool( "load-vscode-snippets", "/config/load_vscode_snippets",
+ i18n( "autocomplete_load_vscode_snippets", "Load VS Code Snippets" ),
+ i18n( "autocomplete_load_vscode_snippets_desc",
+ "Load compatible snippets installed for VS Code." ),
+ true );
+}
+
class SnippetLocatorModel : public Model {
public:
struct Row {
@@ -380,7 +430,7 @@ void AutoCompletePlugin::load( PluginManager* pluginManager ) {
if ( config.contains( "signature_help_documentation" ) )
mSignatureHelpDocumentation = config.value( "signature_help_documentation", true );
else {
- config["suggestion_documentation"] = mSignatureHelpDocumentation;
+ config["signature_help_documentation"] = mSignatureHelpDocumentation;
updateConfigFile = true;
}
diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp
index 1ba503f76..b0abaf69e 100644
--- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp
+++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp
@@ -95,21 +95,38 @@ class AutoCompletePlugin : public Plugin {
bool isReady() const override { return true; }
+ bool hasSettingsPage() const override { return true; }
+
+ void registerSettings( SettingsPage& page ) override;
+
void onRegister( UICodeEditor* ) override;
+
void onUnregister( UICodeEditor* ) override;
+
bool onKeyDown( UICodeEditor*, const KeyEvent& ) override;
+
bool onTextInput( UICodeEditor*, const TextInputEvent& ) override;
+
void update( UICodeEditor* ) override;
+
void postDraw( UICodeEditor*, const Vector2f& startScroll, const Float& lineHeight,
const TextPosition& cursor ) override;
+
void drawBeforeLineText( UICodeEditor*, const Int64&, Vector2f, const Float&,
const Float& ) override;
+
bool onMouseDown( UICodeEditor*, const Vector2i&, const Uint32& ) override;
+
bool onMouseUp( UICodeEditor*, const Vector2i&, const Uint32& ) override;
+
bool onMouseDoubleClick( UICodeEditor*, const Vector2i&, const Uint32& ) override;
+
bool onMouseMove( UICodeEditor*, const Vector2i&, const Uint32& ) override;
+
void onFileSystemEvent( const FileEvent&, const FileInfo& ) override;
+
FileSystemListenerOptions getFileSystemListenerOptions() const override;
+
void onLoadProject( const std::string& projectFolder,
const std::string& projectStatePath ) override;
diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp
index b97a34d79..41ecf2736 100644
--- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp
+++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp
@@ -1,6 +1,7 @@
#include "debuggerplugin.hpp"
#include "../../jsonhelper.hpp"
#include "../../notificationcenter.hpp"
+#include "../../settingspage.hpp"
#include "../../terminalmanager.hpp"
#include "../../uistatusbar.hpp"
#include "../../widgetcommandexecuter.hpp"
@@ -30,6 +31,27 @@ static constexpr auto REQUEST_TYPE_ATTACH = "attach";
namespace ecode {
+void DebuggerPlugin::registerSettings( SettingsPage& page ) {
+ page.addGroup( i18n( "general", "General" ) );
+ page.addBool(
+ "fetch-registers", "/config/fetch_registers",
+ i18n( "debugger_fetch_registers", "Fetch Registers" ),
+ i18n( "debugger_fetch_registers_desc", "Request processor registers while debugging." ),
+ false );
+ page.addBool(
+ "fetch-globals", "/config/fetch_globals",
+ i18n( "debugger_fetch_globals", "Fetch Global Variables" ),
+ i18n( "debugger_fetch_globals_desc", "Request global variables while debugging." ), false );
+ page.addBool( "silent", "/config/silent", i18n( "debugger_silent", "Silent Debugger Logs" ),
+ i18n( "debugger_silent_desc", "Hide non-critical debugger log messages." ),
+ true );
+ page.addBool( "load-vscode-launch-config", "/config/load_vscode_launch_config",
+ i18n( "debugger_load_vscode_launch_config", "Load VS Code Launch Configuration" ),
+ i18n( "debugger_load_vscode_launch_config_desc",
+ "Load compatible launch configurations from .vscode/launch.json." ),
+ true );
+}
+
static constexpr auto INPUT_PATTERN = "%$%{input%:([%w_]+)%}"sv;
static constexpr auto ENV_PATTERN = "%$%{env%:([%w_]+)%}"sv;
static constexpr auto COMMAND_PATTERN = "%$%{command%:([%w_]+)%}"sv;
diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp
index d02802912..0110c2e2c 100644
--- a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp
+++ b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp
@@ -67,6 +67,10 @@ class DebuggerPlugin : public PluginBase {
std::string getDescription() override { return Definition().description; }
+ bool hasSettingsPage() const override { return true; }
+
+ void registerSettings( SettingsPage& page ) override;
+
void onSaveState( IniFile* state ) override;
void onSaveProject( const std::string& projectFolder, const std::string& projectStatePath,
@@ -150,16 +154,27 @@ class DebuggerPlugin : public PluginBase {
mDoc( doc ), mParent( parent ) {}
virtual void onDocumentTextChanged( const DocumentContentChange& ) {}
+
virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& ) {}
+
virtual void onDocumentCursorChange( const TextPosition& ) {}
+
virtual void onDocumentSelectionChange( const TextRange& ) {}
+
virtual void onDocumentLineCountChange( const size_t&, const size_t& ) {}
+
virtual void onDocumentLineChanged( const Int64& ) {}
+
virtual void onDocumentSaved( TextDocument* ) {}
+
virtual void onDocumentClosed( TextDocument* doc ) { onDocumentReset( doc ); }
+
virtual void onDocumentDirtyOnFileSystem( TextDocument* ) {}
+
virtual void onDocumentMoved( TextDocument* ) {}
+
virtual void onDocumentReset( TextDocument* ) {}
+
Client::Type getTextDocumentClientType() { return TextDocument::Client::Auxiliary; }
virtual void onDocumentLineMove( const Int64& fromLine, const Int64& toLine,
diff --git a/src/tools/ecode/plugins/discordRPC/discordRPCplugin.cpp b/src/tools/ecode/plugins/discordRPC/discordRPCplugin.cpp
index 38344aa0a..bf8915f9a 100644
--- a/src/tools/ecode/plugins/discordRPC/discordRPCplugin.cpp
+++ b/src/tools/ecode/plugins/discordRPC/discordRPCplugin.cpp
@@ -1,4 +1,5 @@
#include "discordRPCplugin.hpp"
+#include "../../settingspage.hpp"
using json = nlohmann::json;
@@ -6,6 +7,34 @@ namespace ecode {
static const auto DEFAULT_CLIENT_ID = "1339026777158455336";
static const auto DebounceUniqueId = String::hash( "DiscordRPCplugin::debounce" );
+
+void DiscordRPCplugin::registerSettings( SettingsPage& page ) {
+ auto general = i18n( "general", "General" );
+ page.addGroup( general );
+ page.addText( "application-id", "/config/appID",
+ i18n( "discordrpc_application_id", "Discord Application ID" ),
+ i18n( "discordrpc_application_id_desc",
+ "Discord application identifier used for Rich Presence." ),
+ DEFAULT_CLIENT_ID, {}, false, general );
+ page.addBool( "language-icons", "/config/doLanguageIcons",
+ i18n( "discordrpc_language_icons", "Language Icons" ),
+ i18n( "discordrpc_language_icons_desc",
+ "Display an icon for the active document language." ),
+ true, general );
+ page.addBool( "git-integration", "/config/doGitIntegration",
+ i18n( "discordrpc_git_integration", "Git Integration" ),
+ i18n( "discordrpc_git_integration_desc",
+ "Include repository information in Rich Presence." ),
+ true, general );
+ auto advanced = i18n( "advanced", "Advanced" );
+ page.addGroup( advanced );
+ page.addJsonObject( "icon-bindings", "/config/iconBindings",
+ i18n( "discordrpc_icon_bindings", "Language Icon Bindings" ),
+ i18n( "discordrpc_icon_bindings_desc",
+ "JSON object mapping language identifiers to Discord image assets." ),
+ nlohmann::json::object(), advanced );
+}
+
static const std::vector DiscordRPCCommandList = { "discordrpc-reconnect" };
static const auto DISCORDRPC_DEFAULT_ICON =
diff --git a/src/tools/ecode/plugins/discordRPC/discordRPCplugin.hpp b/src/tools/ecode/plugins/discordRPC/discordRPCplugin.hpp
index 0dd64975d..f1e8613d5 100644
--- a/src/tools/ecode/plugins/discordRPC/discordRPCplugin.hpp
+++ b/src/tools/ecode/plugins/discordRPC/discordRPCplugin.hpp
@@ -43,6 +43,10 @@ class DiscordRPCplugin : public PluginBase {
std::string getDescription() override { return Definition().description; }
+ bool hasSettingsPage() const override { return true; }
+
+ void registerSettings( SettingsPage& page ) override;
+
protected:
DiscordIPC mIPC;
Mutex mDataMutex;
diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.cpp b/src/tools/ecode/plugins/formatter/formatterplugin.cpp
index 144269521..ab8cea7ae 100644
--- a/src/tools/ecode/plugins/formatter/formatterplugin.cpp
+++ b/src/tools/ecode/plugins/formatter/formatterplugin.cpp
@@ -1,5 +1,6 @@
#include "formatterplugin.hpp"
#include "../../notificationcenter.hpp"
+#include "../../settingspage.hpp"
#include
#include
#include
@@ -18,6 +19,15 @@ using json = nlohmann::json;
namespace ecode {
+void FormatterPlugin::registerSettings( SettingsPage& page ) {
+ page.addGroup( i18n( "general", "General" ) );
+ page.addBool( "auto-format-on-save", "/config/auto_format_on_save",
+ i18n( "formatter_auto_format_on_save", "Format on Save" ),
+ i18n( "formatter_auto_format_on_save_desc",
+ "Automatically format supported documents when saving." ),
+ false );
+}
+
Plugin* FormatterPlugin::New( PluginManager* pluginManager ) {
return eeNew( FormatterPlugin, ( pluginManager, false ) );
}
diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.hpp b/src/tools/ecode/plugins/formatter/formatterplugin.hpp
index 422e47bd2..7bb69d563 100644
--- a/src/tools/ecode/plugins/formatter/formatterplugin.hpp
+++ b/src/tools/ecode/plugins/formatter/formatterplugin.hpp
@@ -50,6 +50,10 @@ class FormatterPlugin : public Plugin {
std::string getDescription() { return Definition().description; }
+ bool hasSettingsPage() const override { return true; }
+
+ void registerSettings( SettingsPage& page ) override;
+
virtual String::HashType getConfigFileHash() { return mConfigHash; }
void onRegister( UICodeEditor* );
diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp
index 174e6c3b3..cd24fcaae 100644
--- a/src/tools/ecode/plugins/git/gitplugin.cpp
+++ b/src/tools/ecode/plugins/git/gitplugin.cpp
@@ -1,4 +1,5 @@
#include "gitplugin.hpp"
+#include "../../settingspage.hpp"
#include "gitbranchmodel.hpp"
#include "gitstatusmodel.hpp"
#include
@@ -33,6 +34,45 @@ using json = nlohmann::json;
namespace ecode {
+void GitPlugin::registerSettings( SettingsPage& page ) {
+ page.addGroup( i18n( "general", "General" ) );
+ page.addText( "ui-refresh-frequency", "/config/ui_refresh_frequency",
+ i18n( "git_ui_refresh_frequency", "Refresh Frequency" ),
+ i18n( "git_ui_refresh_frequency_desc",
+ "How often Git information in the interface is refreshed." ),
+ mRefreshFreq.toString(), []( const std::string& text ) {
+ Time value;
+ return SettingsPage::parseNonNegativeSettingsTime( text, value );
+ } );
+ page.addBool( "statusbar-display-branch", "/config/statusbar_display_branch",
+ i18n( "git_statusbar_display_branch", "Show Branch in Status Bar" ),
+ i18n( "git_statusbar_display_branch_desc",
+ "Display the current Git branch in the status bar." ),
+ true );
+ page.addBool( "filetree-highlight-changes", "/config/filetree_highlight_changes",
+ i18n( "git_filetree_highlight_changes", "Highlight File Tree Changes" ),
+ i18n( "git_filetree_highlight_changes_desc",
+ "Highlight files with Git changes in the file tree." ),
+ true );
+ page.addText( "filetree-highlight-style-color", "/config/filetree_highlight_style_color",
+ i18n( "git_filetree_highlight_style_color", "File Tree Highlight Color" ),
+ i18n( "git_filetree_highlight_style_color_desc",
+ "CSS color used to highlight changed files." ),
+ "var(--font-highlight)" );
+ page.addBool( "statusbar-display-modifications", "/config/statusbar_display_modifications",
+ i18n( "git_statusbar_display_modifications", "Show Modifications in Status Bar" ),
+ i18n( "git_statusbar_display_modifications_desc",
+ "Display repository modification counts in the status bar." ),
+ true );
+ page.addBool( "status-recurse-submodules", "/config/status_recurse_submodules",
+ i18n( "git_status_recurse_submodules", "Recurse into Submodules" ),
+ i18n( "git_status_recurse_submodules_desc",
+ "Include submodule changes when collecting repository status." ),
+ true );
+ page.addBool( "silent", "/config/silent", i18n( "git_silent", "Silent Git Logs" ),
+ i18n( "git_silent_desc", "Hide non-critical Git plugin log messages." ), true );
+}
+
static constexpr auto DEFAULT_HIGHLIGHT_COLOR = "var(--font-highlight)"sv;
static constexpr auto GIT_STATUS_UPDATE_TAG = String::hash( "git::status-update" );
diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp
index aa0a92da3..9dac990c7 100644
--- a/src/tools/ecode/plugins/git/gitplugin.hpp
+++ b/src/tools/ecode/plugins/git/gitplugin.hpp
@@ -52,6 +52,10 @@ class GitPlugin : public PluginBase {
std::string getDescription() override { return Definition().description; }
+ bool hasSettingsPage() const override { return true; }
+
+ void registerSettings( SettingsPage& page ) override;
+
void onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) override;
FileSystemListenerOptions getFileSystemListenerOptions() const override;
diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp
index 10ad515c3..18779c5c2 100644
--- a/src/tools/ecode/plugins/linter/linterplugin.cpp
+++ b/src/tools/ecode/plugins/linter/linterplugin.cpp
@@ -1,5 +1,6 @@
#include "linterplugin.hpp"
#include "../../notificationcenter.hpp"
+#include "../../settingspage.hpp"
#include
#include
#include
@@ -30,6 +31,41 @@ using json = nlohmann::json;
namespace ecode {
+void LinterPlugin::registerSettings( SettingsPage& page ) {
+ page.addGroup( i18n( "general", "General" ) );
+ page.addText( "delay-time", "/config/delay_time", i18n( "linter_delay_time", "Lint Delay" ),
+ i18n( "linter_delay_time_desc", "Time to wait before linting after an edit." ),
+ getDelayTime().toString(), []( const std::string& text ) {
+ Time value;
+ return SettingsPage::parseNonNegativeSettingsTime( text, value );
+ } );
+ page.addBool( "enable-lsp-diagnostics", "/config/enable_lsp_diagnostics",
+ i18n( "linter_enable_lsp_diagnostics", "Language Server Diagnostics" ),
+ i18n( "linter_enable_lsp_diagnostics_desc",
+ "Display diagnostics reported by language servers." ),
+ true );
+ page.addBool( "enable-error-lens", "/config/enable_error_lens",
+ i18n( "linter_enable_error_lens", "Error Lens" ),
+ i18n( "linter_enable_error_lens_desc",
+ "Display diagnostic messages inline in the editor." ),
+ true );
+ page.addStringList(
+ "disable-lsp-languages", "/config/disable_lsp_languages",
+ i18n( "linter_disable_lsp_languages", "Disable LSP Diagnostic Languages" ),
+ i18n( "linter_disable_lsp_languages_desc",
+ "Comma-separated language identifiers where LSP diagnostics are disabled." ) );
+ page.addStringList(
+ "disable-languages", "/config/disable_languages",
+ i18n( "linter_disable_languages", "Disable Linter Languages" ),
+ i18n( "linter_disable_languages_desc",
+ "Comma-separated language identifiers where linters are disabled." ) );
+ page.addBool( "goto-ignore-warnings", "/config/goto_ignore_warnings",
+ i18n( "linter_goto_ignore_warnings", "Ignore Warnings When Navigating" ),
+ i18n( "linter_goto_ignore_warnings_desc",
+ "Skip warnings when navigating between diagnostics." ),
+ false );
+}
+
Plugin* LinterPlugin::New( PluginManager* pluginManager ) {
return eeNew( LinterPlugin, ( pluginManager, false ) );
}
@@ -1145,9 +1181,8 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index,
Color wcolor(
editor->getColorScheme().getEditorSyntaxStyle( "warning"_sst ).color );
if ( mLightbulbIcon == nullptr )
- mLightbulbIcon =
- editor->getUISceneNode()->getUIIconThemeManager()->findIcon(
- "lightbulb-autofix" );
+ mLightbulbIcon = editor->getUISceneNode()->getUIIconThemeManager()->findIcon(
+ "lightbulb-autofix" );
if ( nullptr != mLightbulbIcon ) {
const int iconSize = (int)eefloor( lineHeight );
Drawable* drawable = mLightbulbIcon->getSource( iconSize ).get();
diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp
index c1d9976b3..3d0548273 100644
--- a/src/tools/ecode/plugins/linter/linterplugin.hpp
+++ b/src/tools/ecode/plugins/linter/linterplugin.hpp
@@ -76,6 +76,10 @@ class LinterPlugin : public Plugin {
std::string getDescription() { return Definition().description; }
+ bool hasSettingsPage() const override { return true; }
+
+ void registerSettings( SettingsPage& page ) override;
+
virtual String::HashType getConfigFileHash() { return mConfigHash; }
void onRegister( UICodeEditor* );
diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp
index 19ac79f69..bd842d8ac 100644
--- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp
+++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp
@@ -1,5 +1,6 @@
#include "lspclientplugin.hpp"
#include "../../notificationcenter.hpp"
+#include "../../settingspage.hpp"
#include "../../version.hpp"
#include
#include
@@ -26,6 +27,52 @@ using json = nlohmann::json;
namespace ecode {
+void LSPClientPlugin::registerSettings( SettingsPage& page ) {
+ page.addGroup( i18n( "general", "General" ) );
+ page.addText( "hover-delay", "/config/hover_delay", i18n( "lsp_hover_delay", "Hover Delay" ),
+ i18n( "lsp_hover_delay_desc",
+ "Time to wait before showing language server information while hovering." ),
+ mHoverDelay.toString(), []( const std::string& text ) {
+ Time value;
+ return SettingsPage::parseNonNegativeSettingsTime( text, value );
+ } );
+ page.addText( "server-close-after-idle-time", "/config/server_close_after_idle_time",
+ i18n( "lsp_server_close_after_idle_time", "Server Close After Idle Time" ),
+ i18n( "lsp_server_close_after_idle_time_desc",
+ "Time to keep an unused language server running before closing it." ),
+ mClientManager.getLSPDecayTime().toString(), []( const std::string& text ) {
+ Time value;
+ return SettingsPage::parseNonNegativeSettingsTime( text, value );
+ } );
+ page.addBool( "silent", "/config/silent", i18n( "lsp_silent", "Silent Language Server Logs" ),
+ i18n( "lsp_silent_desc", "Hide non-critical language server log messages." ),
+ mSilence );
+ page.addBool(
+ "trim-logs", "/config/trim_logs", i18n( "lsp_trim_logs", "Trim Language Server Logs" ),
+ i18n( "lsp_trim_logs_desc", "Limit each language server log line to 1 KiB." ), mTrimLogs );
+ page.addBool( "semantic-highlighting", "/config/semantic_highlighting",
+ i18n( "lsp_semantic_highlighting", "Semantic Highlighting" ),
+ i18n( "lsp_semantic_highlighting_desc",
+ "Use semantic tokens provided by language servers when available." ),
+ mSemanticHighlighting );
+ page.addStringList(
+ "disable-semantic-highlighting-lang", "/config/disable_semantic_highlighting_lang",
+ i18n( "lsp_disable_semantic_highlighting_lang", "Disable Semantic Highlighting Languages" ),
+ i18n( "lsp_disable_semantic_highlighting_lang_desc",
+ "Comma-separated language identifiers where semantic highlighting is disabled." ) );
+ page.addBool( "breadcrumb-navigation", "/config/breadcrumb_navigation",
+ i18n( "lsp_breadcrumb_navigation", "Breadcrumb Navigation" ),
+ i18n( "lsp_breadcrumb_navigation_desc",
+ "Display language-aware breadcrumb navigation above the editor." ),
+ mBreadcrumb );
+ page.addText(
+ "breadcrumb-height", "/config/breadcrumb_height",
+ i18n( "lsp_breadcrumb_height", "Breadcrumb Height" ),
+ i18n( "lsp_breadcrumb_height_desc", "Height of the editor breadcrumb using a CSS length." ),
+ mBreadcrumbHeight.toString(),
+ []( const std::string& text ) { return StyleSheetLength::isLength( text ); } );
+}
+
static Action::UniqueID getMouseMoveHash( UICodeEditor* editor ) {
return hashCombine( String::hash( "LSPClientPlugin::onMouseMove-" ),
reinterpret_cast( editor ) );
@@ -1982,8 +2029,8 @@ void LSPClientPlugin::drawTop( UICodeEditor* editor, const Vector2f& screenStart
Vector2f iconPos( { pos.x, screenStart.y + textOffsetY +
eefloor( ( textHeight - iconHeight ) * 0.5f ) } );
separatorDrawable->draw( iconPos );
- pos.x +=
- separatorDrawable->getPixelsSize().getWidth() + eefloor( PixelDensity::dpToPx( 8 ) );
+ pos.x += separatorDrawable->getPixelsSize().getWidth() +
+ eefloor( PixelDensity::dpToPx( 8 ) );
separatorDrawable->setColor( c );
} else {
pos.x += eefloor( PixelDensity::dpToPx( 16 ) );
diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp
index 4d8fac0c4..c3520b5f0 100644
--- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp
+++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp
@@ -46,6 +46,10 @@ class LSPClientPlugin : public Plugin {
virtual String::HashType getConfigFileHash() { return mConfigHash; }
+ bool hasSettingsPage() const { return true; }
+
+ void registerSettings( SettingsPage& page );
+
void onRegister( UICodeEditor* );
void onUnregister( UICodeEditor* );
diff --git a/src/tools/ecode/plugins/plugin.hpp b/src/tools/ecode/plugins/plugin.hpp
index 4b6b70e3b..27407f7d5 100644
--- a/src/tools/ecode/plugins/plugin.hpp
+++ b/src/tools/ecode/plugins/plugin.hpp
@@ -26,6 +26,7 @@ namespace ecode {
class PluginManager;
class PluginContextProvider;
+class SettingsPage;
class Plugin : public UICodeEditorPlugin {
public:
@@ -45,6 +46,10 @@ class Plugin : public UICodeEditorPlugin {
virtual std::string getFileConfigPath();
+ virtual bool hasSettingsPage() const { return false; }
+
+ virtual void registerSettings( SettingsPage& ) {}
+
PluginManager* getManager() const;
PluginContextProvider* getPluginContext() const;
diff --git a/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp b/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp
index f1a0f4f2b..cd1c3f558 100644
--- a/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp
+++ b/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp
@@ -1,4 +1,5 @@
#include "spellcheckerplugin.hpp"
+#include "../../settingspage.hpp"
#include "eepp/ui/abstract/uiabstractview.hpp"
#include "eepp/ui/models/itemlistmodel.hpp"
#include "eepp/window/engine.hpp"
@@ -15,6 +16,18 @@ using json = nlohmann::json;
namespace ecode {
+void SpellCheckerPlugin::registerSettings( SettingsPage& page ) {
+ page.addGroup( i18n( "general", "General" ) );
+ page.addText( "delay-time", "/config/delay_time",
+ i18n( "spellchecker_delay_time", "Spell Check Delay" ),
+ i18n( "spellchecker_delay_time_desc",
+ "Time to wait before checking spelling after an edit." ),
+ getDelayTime().toString(), []( const std::string& text ) {
+ Time value;
+ return SettingsPage::parseNonNegativeSettingsTime( text, value );
+ } );
+}
+
static constexpr auto SPELL_CHECKER_CMD = "typos";
static constexpr auto SPELL_CHECKER_ARGS = "--format=brief";
diff --git a/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.hpp b/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.hpp
index e3b005255..75912dea5 100644
--- a/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.hpp
+++ b/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.hpp
@@ -39,6 +39,10 @@ class SpellCheckerPlugin : public PluginBase {
std::string getDescription() override { return Definition().description; }
+ bool hasSettingsPage() const override { return true; }
+
+ void registerSettings( SettingsPage& page ) override;
+
void drawAfterLineText( UICodeEditor* editor, const Int64& index, Vector2f position,
const Float& fontSize, const Float& lineHeight ) override;
@@ -98,7 +102,8 @@ class SpellCheckerPlugin : public PluginBase {
void invalidateEditors( TextDocument* doc );
- std::optional getMatchFromTextPosition( UICodeEditor* editor, TextPosition pos );
+ std::optional getMatchFromTextPosition( UICodeEditor* editor,
+ TextPosition pos );
std::optional getMatchFromScreenPos( UICodeEditor* editor, Vector2f pos );
@@ -109,7 +114,6 @@ class SpellCheckerPlugin : public PluginBase {
void goToNextError( UICodeEditor* editor );
void goToPrevError( UICodeEditor* editor );
-
};
} // namespace ecode
diff --git a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp
index 39a3450b5..2c2f4be12 100644
--- a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp
+++ b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp
@@ -1,4 +1,5 @@
#include "xmltoolsplugin.hpp"
+#include "../../settingspage.hpp"
#include
#include
#include
@@ -8,6 +9,20 @@ using json = nlohmann::json;
namespace ecode {
+void XMLToolsPlugin::registerSettings( SettingsPage& page ) {
+ page.addGroup( i18n( "general", "General" ) );
+ page.addBool( "highlight-match", "/config/highlight_match",
+ i18n( "xmltools_highlight_match", "Highlight Matching Tags" ),
+ i18n( "xmltools_highlight_match_desc",
+ "Highlight the matching XML or HTML tag at the cursor." ),
+ true );
+ page.addBool( "auto-edit-match", "/config/auto_edit_match",
+ i18n( "xmltools_auto_edit_match", "Edit Matching Tags" ),
+ i18n( "xmltools_auto_edit_match_desc",
+ "Update the matching tag automatically while editing." ),
+ true );
+}
+
Plugin* XMLToolsPlugin::New( PluginManager* pluginManager ) {
return eeNew( XMLToolsPlugin, ( pluginManager, false ) );
}
diff --git a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp
index d1ebc0736..0f951963b 100644
--- a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp
+++ b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp
@@ -36,6 +36,10 @@ class XMLToolsPlugin : public PluginBase {
std::string getDescription() override { return Definition().description; }
+ bool hasSettingsPage() const override { return true; }
+
+ void registerSettings( SettingsPage& page ) override;
+
bool getHighlightMatch() const;
bool getAutoEditMatch() const;
@@ -73,17 +77,29 @@ class XMLToolsPlugin : public PluginBase {
mDoc( doc ), mParent( parent ) {}
virtual void onDocumentTextChanged( const DocumentContentChange& );
+
virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& ) {}
+
virtual void onDocumentCursorChange( const TextPosition& ) {}
+
virtual void onDocumentInterestingCursorChange( const TextPosition& ) {}
+
virtual void onDocumentSelectionChange( const TextRange& );
+
virtual void onDocumentLineCountChange( const size_t&, const size_t& ) {}
+
virtual void onDocumentLineChanged( const Int64& ) {}
+
virtual void onDocumentSaved( TextDocument* ) {}
+
virtual void onDocumentClosed( TextDocument* doc ) { onDocumentReset( doc ); }
+
virtual void onDocumentDirtyOnFileSystem( TextDocument* ) {}
+
virtual void onDocumentMoved( TextDocument* ) {};
+
virtual void onDocumentReset( TextDocument* ) { mSelections.clear(); }
+
Client::Type getTextDocumentClientType() { return TextDocument::Client::Auxiliary; }
protected:
diff --git a/src/tools/ecode/settingsdocument.cpp b/src/tools/ecode/settingsdocument.cpp
new file mode 100644
index 000000000..e2809de52
--- /dev/null
+++ b/src/tools/ecode/settingsdocument.cpp
@@ -0,0 +1,78 @@
+#include "settingsdocument.hpp"
+#include
+
+using namespace EE::System;
+
+namespace ecode {
+
+SettingsDocument::SettingsDocument( std::string path, nlohmann::json data ) :
+ mPath( std::move( path ) ), mOriginal( data ), mData( std::move( data ) ) {}
+
+std::shared_ptr SettingsDocument::load( std::string path, std::string& error ) {
+ std::string contents;
+ if ( !FileSystem::fileGet( path, contents ) ) {
+ error = "Unable to read settings file";
+ return {};
+ }
+ try {
+ auto data = nlohmann::json::parse( contents, nullptr, true, true );
+ if ( !data.is_object() ) {
+ error = "Settings file root must be an object";
+ return {};
+ }
+ return std::shared_ptr(
+ new SettingsDocument( std::move( path ), std::move( data ) ) );
+ } catch ( const nlohmann::json::exception& exception ) {
+ error = exception.what();
+ return {};
+ }
+}
+
+bool SettingsDocument::save() {
+ if ( !mDirty )
+ return true;
+ if ( !FileSystem::fileWrite( mPath, mData.dump( 2 ) ) )
+ return false;
+ mOriginal = mData;
+ mDirty = false;
+ return true;
+}
+
+nlohmann::json SettingsDocument::getValue( const std::string& pointer,
+ nlohmann::json fallback ) const {
+ try {
+ const auto path = nlohmann::json::json_pointer( pointer );
+ if ( mData.contains( path ) )
+ return mData.at( path );
+ } catch ( const nlohmann::json::exception& ) {
+ }
+ return fallback;
+}
+
+bool SettingsDocument::setValue( const std::string& pointer, nlohmann::json value ) {
+ try {
+ const auto path = nlohmann::json::json_pointer( pointer );
+ if ( mData.contains( path ) && mData.at( path ) == value )
+ return false;
+ mData[path] = std::move( value );
+ mDirty = true;
+ return true;
+ } catch ( const nlohmann::json::exception& ) {
+ return false;
+ }
+}
+
+bool SettingsDocument::contains( const std::string& pointer ) const {
+ try {
+ return mData.contains( nlohmann::json::json_pointer( pointer ) );
+ } catch ( const nlohmann::json::exception& ) {
+ return false;
+ }
+}
+
+void SettingsDocument::discard() {
+ mData = mOriginal;
+ mDirty = false;
+}
+
+} // namespace ecode
diff --git a/src/tools/ecode/settingsdocument.hpp b/src/tools/ecode/settingsdocument.hpp
new file mode 100644
index 000000000..c25ebda84
--- /dev/null
+++ b/src/tools/ecode/settingsdocument.hpp
@@ -0,0 +1,62 @@
+#ifndef ECODE_SETTINGSDOCUMENT_HPP
+#define ECODE_SETTINGSDOCUMENT_HPP
+
+#include
+#include
+#include
+
+namespace ecode {
+
+class SettingsDocument {
+ public:
+ static std::shared_ptr load( std::string path, std::string& error );
+
+ template T get( const std::string& pointer, T fallback ) const {
+ try {
+ const auto path = nlohmann::json::json_pointer( pointer );
+ if ( mData.contains( path ) )
+ return mData.at( path ).get();
+ } catch ( const nlohmann::json::exception& ) {
+ }
+ return fallback;
+ }
+
+ template bool set( const std::string& pointer, T value ) {
+ try {
+ const auto path = nlohmann::json::json_pointer( pointer );
+ if ( mData.contains( path ) && mData.at( path ) == value )
+ return false;
+ mData[path] = std::move( value );
+ mDirty = true;
+ return true;
+ } catch ( const nlohmann::json::exception& ) {
+ return false;
+ }
+ }
+
+ nlohmann::json getValue( const std::string& pointer, nlohmann::json fallback ) const;
+
+ bool setValue( const std::string& pointer, nlohmann::json value );
+
+ bool contains( const std::string& pointer ) const;
+
+ bool save();
+
+ void discard();
+
+ bool isDirty() const { return mDirty; }
+
+ const std::string& path() const { return mPath; }
+
+ private:
+ SettingsDocument( std::string path, nlohmann::json data );
+
+ std::string mPath;
+ nlohmann::json mOriginal;
+ nlohmann::json mData;
+ bool mDirty{ false };
+};
+
+} // namespace ecode
+
+#endif
diff --git a/src/tools/ecode/settingsmodel.hpp b/src/tools/ecode/settingsmodel.hpp
index 08b144cef..2a2354146 100644
--- a/src/tools/ecode/settingsmodel.hpp
+++ b/src/tools/ecode/settingsmodel.hpp
@@ -52,6 +52,7 @@ struct TextSetting {
std::function get;
std::function set;
bool commitOnFocusLoss{ false };
+ bool password{ false };
};
struct FloatSetting {
@@ -129,8 +130,11 @@ class SettingsModel {
}
const std::vector& categories() const { return mCategories; }
+
const std::vector& groups() const { return mGroups; }
+
std::vector& settings() { return mSettings; }
+
const std::vector& settings() const { return mSettings; }
private:
diff --git a/src/tools/ecode/settingspage.cpp b/src/tools/ecode/settingspage.cpp
new file mode 100644
index 000000000..b396b5547
--- /dev/null
+++ b/src/tools/ecode/settingspage.cpp
@@ -0,0 +1,245 @@
+#include "settingspage.hpp"
+
+namespace ecode {
+
+bool SettingsPage::parseNonNegativeSettingsTime( const std::string& text, Time& time ) {
+ auto parts = String::split( text, " " );
+ bool hasValue = false;
+ for ( auto& part : parts ) {
+ String::trimInPlace( part );
+ if ( part.empty() )
+ continue;
+ size_t suffixLength =
+ String::endsWith( part, "ms" )
+ ? 2
+ : ( String::endsWith( part, "s" ) || String::endsWith( part, "m" ) ? 1 : 0 );
+ std::string number = part.substr( 0, part.size() - suffixLength );
+ double value;
+ if ( number.empty() || !String::fromString( value, number ) || value < 0 )
+ return false;
+ hasValue = true;
+ }
+ if ( !hasValue )
+ return false;
+ time = Time::fromString( text );
+ return true;
+}
+
+SettingsPage::SettingsPage( SettingsModel& model, std::shared_ptr document,
+ std::string category, std::string idPrefix ) :
+ mModel( model ),
+ mDocument( std::move( document ) ),
+ mCategory( std::move( category ) ),
+ mIdPrefix( std::move( idPrefix ) ) {}
+
+bool SettingsPage::addGroup( String name ) {
+ return mModel.addGroup( { mCategory, std::move( name ), mModel.settings().size() } );
+}
+
+bool SettingsPage::addBool( std::string id, std::string jsonPointer, String name,
+ String description, bool defaultValue, String group ) {
+ if ( !mDocument )
+ return false;
+ auto document = mDocument;
+ return mModel.addSetting( { { mIdPrefix + "." + id, mCategory, std::move( name ),
+ std::move( description ), std::move( group ) },
+ BoolSetting{ [document, jsonPointer, defaultValue] {
+ return document->get( jsonPointer,
+ defaultValue );
+ },
+ [document, jsonPointer]( bool value ) {
+ document->set( jsonPointer, value );
+ } } } );
+}
+
+bool SettingsPage::addChoice( std::string id, std::string jsonPointer, String name,
+ String description, std::vector choices,
+ std::vector values, nlohmann::json defaultValue,
+ std::vector choiceDescriptions, String group ) {
+ if ( !mDocument || choices.empty() || choices.size() != values.size() )
+ return false;
+ auto document = mDocument;
+ auto mappedValues = std::make_shared>( std::move( values ) );
+ return mModel.addSetting(
+ { { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
+ std::move( group ) },
+ ChoiceSetting{
+ std::move( choices ), std::move( choiceDescriptions ),
+ [document, jsonPointer, mappedValues, defaultValue = std::move( defaultValue )] {
+ const auto current = document->getValue( jsonPointer, defaultValue );
+ auto found = std::find( mappedValues->begin(), mappedValues->end(), current );
+ return found == mappedValues->end()
+ ? size_t{ 0 }
+ : static_cast( std::distance( mappedValues->begin(), found ) );
+ },
+ [document, jsonPointer, mappedValues]( size_t selected ) {
+ if ( selected < mappedValues->size() )
+ document->setValue( jsonPointer, ( *mappedValues )[selected] );
+ } } } );
+}
+
+bool SettingsPage::addEditableChoice( std::string id, std::string jsonPointer, String name,
+ String description, std::vector choices,
+ std::string defaultValue,
+ std::function validate,
+ String group ) {
+ if ( !mDocument )
+ return false;
+ auto document = mDocument;
+ return mModel.addSetting(
+ { { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
+ std::move( group ) },
+ EditableChoiceSetting{
+ std::move( choices ),
+ [document, jsonPointer, defaultValue = std::move( defaultValue )] {
+ return String::fromUtf8(
+ document->get( jsonPointer, defaultValue ) );
+ },
+ [document, jsonPointer, validate = std::move( validate )]( const String& value ) {
+ const std::string text = value.toUtf8();
+ if ( validate && !validate( text ) )
+ return false;
+ document->set( jsonPointer, text );
+ return true;
+ } } } );
+}
+
+bool SettingsPage::addInteger( std::string id, std::string jsonPointer, String name,
+ String description, int min, int max, int defaultValue,
+ String group ) {
+ if ( !mDocument || min > max )
+ return false;
+ auto document = mDocument;
+ return mModel.addSetting( { { mIdPrefix + "." + id, mCategory, std::move( name ),
+ std::move( description ), std::move( group ) },
+ IntegerSetting{ min, max,
+ [document, jsonPointer, defaultValue] {
+ return document->get( jsonPointer,
+ defaultValue );
+ },
+ [document, jsonPointer]( int value ) {
+ document->set( jsonPointer, value );
+ } } } );
+}
+
+bool SettingsPage::addText( std::string id, std::string jsonPointer, String name,
+ String description, std::string defaultValue,
+ std::function validate,
+ bool commitOnFocusLoss, String group ) {
+ if ( !mDocument )
+ return false;
+ auto document = mDocument;
+ return mModel.addSetting(
+ { { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
+ std::move( group ) },
+ TextSetting{ [document, jsonPointer, defaultValue = std::move( defaultValue )] {
+ return document->get( jsonPointer, defaultValue );
+ },
+ [document, jsonPointer,
+ validate = std::move( validate )]( const std::string& value ) {
+ if ( validate && !validate( value ) )
+ return false;
+ document->set( jsonPointer, value );
+ return true;
+ },
+ commitOnFocusLoss } } );
+}
+
+bool SettingsPage::addSecret( std::string id, std::string jsonPointer, String name,
+ String description, std::string defaultValue, String group,
+ std::string legacyJsonPointer ) {
+ if ( !mDocument )
+ return false;
+ auto document = mDocument;
+ return mModel.addSetting(
+ { { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
+ std::move( group ) },
+ TextSetting{
+ [document, jsonPointer, legacyJsonPointer, defaultValue = std::move( defaultValue )] {
+ if ( !legacyJsonPointer.empty() && !document->contains( jsonPointer ) )
+ return document->get( legacyJsonPointer, defaultValue );
+ return document->get( jsonPointer, defaultValue );
+ },
+ [document, jsonPointer]( const std::string& value ) {
+ document->set( jsonPointer, value );
+ return true;
+ },
+ true, true } } );
+}
+
+bool SettingsPage::addJsonObject( std::string id, std::string jsonPointer, String name,
+ String description, nlohmann::json defaultValue, String group ) {
+ if ( !mDocument || !defaultValue.is_object() )
+ return false;
+ auto document = mDocument;
+ return mModel.addSetting(
+ { { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
+ std::move( group ) },
+ TextSetting{ [document, jsonPointer, defaultValue = std::move( defaultValue )] {
+ return document->getValue( jsonPointer, defaultValue ).dump();
+ },
+ [document, jsonPointer]( const std::string& value ) {
+ auto parsed = nlohmann::json::parse( value, nullptr, false );
+ if ( !parsed.is_object() )
+ return false;
+ document->setValue( jsonPointer, std::move( parsed ) );
+ return true;
+ },
+ true } } );
+}
+
+bool SettingsPage::addStringList( std::string id, std::string jsonPointer, String name,
+ String description, std::vector defaultValue,
+ String group ) {
+ if ( !mDocument )
+ return false;
+ auto document = mDocument;
+ return mModel.addSetting(
+ { { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
+ std::move( group ) },
+ TextSetting{ [document, jsonPointer, defaultValue = std::move( defaultValue )] {
+ return String::join(
+ document->get>( jsonPointer, defaultValue ),
+ ',' );
+ },
+ [document, jsonPointer]( const std::string& value ) {
+ std::vector values;
+ for ( auto& item : String::split( value, "," ) ) {
+ String::trimInPlace( item );
+ if ( !item.empty() )
+ values.emplace_back( std::move( item ) );
+ }
+ document->set( jsonPointer, std::move( values ) );
+ return true;
+ },
+ true } } );
+}
+
+bool SettingsPage::addFloat( std::string id, std::string jsonPointer, String name,
+ String description, double min, double max, double step,
+ double defaultValue, String group ) {
+ if ( !mDocument || min > max || step <= 0 )
+ return false;
+ auto document = mDocument;
+ return mModel.addSetting( { { mIdPrefix + "." + id, mCategory, std::move( name ),
+ std::move( description ), std::move( group ) },
+ FloatSetting{ min, max, step,
+ [document, jsonPointer, defaultValue] {
+ return document->get( jsonPointer,
+ defaultValue );
+ },
+ [document, jsonPointer]( double value ) {
+ document->set( jsonPointer, value );
+ } } } );
+}
+
+bool SettingsPage::addAction( std::string id, String name, String description, String buttonText,
+ std::function action, String group ) {
+ if ( !action )
+ return false;
+ return mModel.addSetting( { { mIdPrefix + "." + id, mCategory, std::move( name ),
+ std::move( description ), std::move( group ) },
+ ActionSetting{ std::move( buttonText ), std::move( action ) } } );
+}
+
+} // namespace ecode
diff --git a/src/tools/ecode/settingspage.hpp b/src/tools/ecode/settingspage.hpp
new file mode 100644
index 000000000..b12d96f6b
--- /dev/null
+++ b/src/tools/ecode/settingspage.hpp
@@ -0,0 +1,64 @@
+#ifndef ECODE_SETTINGSPAGE_HPP
+#define ECODE_SETTINGSPAGE_HPP
+
+#include "settingsdocument.hpp"
+#include "settingsmodel.hpp"
+
+namespace ecode {
+
+class SettingsPage {
+ public:
+ static bool parseNonNegativeSettingsTime( const std::string& text, Time& time );
+
+ SettingsPage( SettingsModel& model, std::shared_ptr document,
+ std::string category, std::string idPrefix );
+
+ bool addGroup( String name );
+
+ bool addBool( std::string id, std::string jsonPointer, String name, String description,
+ bool defaultValue, String group = {} );
+
+ bool addChoice( std::string id, std::string jsonPointer, String name, String description,
+ std::vector choices, std::vector values,
+ nlohmann::json defaultValue, std::vector choiceDescriptions = {},
+ String group = {} );
+
+ bool addEditableChoice( std::string id, std::string jsonPointer, String name,
+ String description, std::vector choices,
+ std::string defaultValue,
+ std::function validate = {},
+ String group = {} );
+
+ bool addInteger( std::string id, std::string jsonPointer, String name, String description,
+ int min, int max, int defaultValue, String group = {} );
+
+ bool addText( std::string id, std::string jsonPointer, String name, String description,
+ std::string defaultValue, std::function validate = {},
+ bool commitOnFocusLoss = false, String group = {} );
+
+ bool addSecret( std::string id, std::string jsonPointer, String name, String description,
+ std::string defaultValue = {}, String group = {},
+ std::string legacyJsonPointer = {} );
+
+ bool addJsonObject( std::string id, std::string jsonPointer, String name, String description,
+ nlohmann::json defaultValue = nlohmann::json::object(), String group = {} );
+
+ bool addStringList( std::string id, std::string jsonPointer, String name, String description,
+ std::vector defaultValue = {}, String group = {} );
+
+ bool addFloat( std::string id, std::string jsonPointer, String name, String description,
+ double min, double max, double step, double defaultValue, String group = {} );
+
+ bool addAction( std::string id, String name, String description, String buttonText,
+ std::function action, String group = {} );
+
+ private:
+ SettingsModel& mModel;
+ std::shared_ptr mDocument;
+ std::string mCategory;
+ std::string mIdPrefix;
+};
+
+} // namespace ecode
+
+#endif
diff --git a/src/tools/ecode/settingspanel.cpp b/src/tools/ecode/settingspanel.cpp
index 8ccbf9351..883b33e8d 100644
--- a/src/tools/ecode/settingspanel.cpp
+++ b/src/tools/ecode/settingspanel.cpp
@@ -1,6 +1,10 @@
#include "settingspanel.hpp"
#include "datetimecontroller.hpp"
#include "ecode.hpp"
+#include "plugins/plugin.hpp"
+#include "plugins/pluginmanager.hpp"
+#include "settingsdocument.hpp"
+#include "settingspage.hpp"
#include "uitreeviewfs.hpp"
#include
#include
@@ -220,6 +224,9 @@ static constexpr const char* SETTINGS_PANEL_LAYOUT = R"xml(
layout-width: 210dp;
layout-height: wrap_content;
}
+.settings_panel .settings_text.error {
+ border-color: var(--theme-error);
+}
.settings_panel .settings_integer {
layout-width: 110dp;
layout-height: wrap_content;
@@ -230,7 +237,7 @@ static constexpr const char* SETTINGS_PANEL_LAYOUT = R"xml(
@@ -327,6 +334,7 @@ void SettingsPanel::PanelState::reset() {
materializedCategories.clear();
subcategoryHeadings.clear();
model.clear();
+ documents.clear();
settingViews.clear();
selectedCategory.clear();
categoryFilter.clear();
@@ -426,10 +434,12 @@ void SettingsPanel::create( Scope scope ) {
panel.window->getKeyBindings().addKeybind(
{ KEY_E, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "focusSettingsCategories" );
panel.settings->beginAttributesTransaction();
- if ( scope == Scope::User )
+ if ( scope == Scope::User ) {
addUserSettings( panel );
- else
+ addPluginSettings( panel );
+ } else {
addProjectSettings( panel );
+ }
materializeCategory( panel, panel.selectedCategory );
setupCategories( panel );
panel.settings->endAttributesTransaction();
@@ -459,6 +469,10 @@ void SettingsPanel::create( Scope scope ) {
} );
panel.connections +=
panel.window->connect( Event::OnWindowClose, [this, &panel, scope]( const Event* ) {
+ for ( const auto& document : panel.documents ) {
+ if ( !document->save() )
+ Log::error( "Could not save settings document: %s", document->path() );
+ }
if ( scope == Scope::User )
mApp->saveConfig();
else
@@ -501,7 +515,6 @@ void SettingsPanel::addCategory( PanelState& panel, const std::string& id, const
void SettingsPanel::addSubcategoryHeading( PanelState& panel, const std::string& category,
const String& name ) {
panel.model.addGroup( { category, name, panel.model.settings().size() } );
- panel.subcategoryHeadings.push_back( { category, name, nullptr } );
}
void SettingsPanel::setupCategories( PanelState& panel ) {
@@ -636,14 +649,7 @@ void SettingsPanel::materializeCategory( PanelState& panel, const std::string& c
->loadLayoutFromString( SETTINGS_SUBCATEGORY_HEADING_LAYOUT, container->second )
->asType();
heading->setText( group.name );
- auto subcategory =
- std::find_if( panel.subcategoryHeadings.begin(), panel.subcategoryHeadings.end(),
- [&group]( const auto& item ) {
- return item.category == group.category &&
- item.name == group.name && !item.heading;
- } );
- if ( subcategory != panel.subcategoryHeadings.end() )
- subcategory->heading = heading;
+ panel.subcategoryHeadings.push_back( { group.category, group.name, heading } );
}
auto& view = panel.settingViews[i];
if ( auto* value = std::get_if( &setting.value ) ) {
@@ -712,6 +718,8 @@ void SettingsPanel::materializeCategory( PanelState& panel, const std::string& c
} else if ( auto* value = std::get_if( &setting.value ) ) {
auto* row = createRow( panel, setting, view, SETTINGS_TEXT_ROW_LAYOUT.root() );
auto* input = row->find( "setting_control_widget" );
+ if ( value->password )
+ input->setMode( UITextInput::TextInputMode::Password );
input->setText( String::fromUtf8( value->get() ) );
auto commit = [input, value]( const Event* ) {
if ( !value->set( input->getText().toUtf8() ) ) {
@@ -787,29 +795,6 @@ static void setNodeTreeEnabled( Node* node, bool enabled ) {
setNodeTreeEnabled( child, enabled );
}
-static bool parseNonNegativeTime( const std::string& text, Time& time ) {
- auto parts = String::split( text, " " );
- bool hasValue = false;
- for ( auto& part : parts ) {
- String::trimInPlace( part );
- if ( part.empty() )
- continue;
- size_t suffixLength =
- String::endsWith( part, "ms" )
- ? 2
- : ( String::endsWith( part, "s" ) || String::endsWith( part, "m" ) ? 1 : 0 );
- std::string number = part.substr( 0, part.size() - suffixLength );
- double value;
- if ( number.empty() || !String::fromString( value, number ) || value < 0 )
- return false;
- hasValue = true;
- }
- if ( !hasValue )
- return false;
- time = Time::fromString( text );
- return true;
-}
-
void SettingsPanel::setCategoryEnabled( PanelState& panel, const std::string& category,
bool enabled, const std::string& excludedSetting ) {
auto& settings = panel.model.settings();
@@ -1633,7 +1618,7 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
[this] { return mApp->getConfig().editor.cursorBlinkingTime.toString(); },
[this]( const std::string& text ) {
Time value;
- if ( !parseNonNegativeTime( text, value ) )
+ if ( !SettingsPage::parseNonNegativeSettingsTime( text, value ) )
return false;
mApp->getConfig().editor.cursorBlinkingTime = value;
mApp->getSplitter()->forEachEditor(
@@ -1695,7 +1680,8 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
[this] { return mApp->getConfig().editor.codeFoldingRefreshFreq.toString(); },
[this]( const std::string& text ) {
Time value;
- if ( !parseNonNegativeTime( text, value ) || value < Seconds( 1 ) )
+ if ( !SettingsPage::parseNonNegativeSettingsTime( text, value ) ||
+ value < Seconds( 1 ) )
return false;
mApp->getConfig().editor.codeFoldingRefreshFreq = value;
mApp->getSplitter()->forEachEditor(
@@ -2036,6 +2022,42 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
"Keep the terminal scrollbar hidden." ) } );
}
+void SettingsPanel::addPluginSettings( PanelState& panel ) {
+ auto* manager = mApp->getPluginManager();
+ if ( !manager )
+ return;
+ manager->forEachPlugin( [this, &panel]( Plugin* plugin ) {
+ if ( !plugin || !plugin->isReady() || !plugin->hasSettingsPage() ||
+ !plugin->hasFileConfig() )
+ return;
+ std::string error;
+ auto document = SettingsDocument::load( plugin->getFileConfigPath(), error );
+ if ( !document ) {
+ Log::warning( "Could not load settings for plugin %s: %s", plugin->getId(), error );
+ return;
+ }
+ const std::string category = "plugins." + plugin->getId();
+ addCategory( panel, category, mApp->i18n( "plugins", "Plugins" ), plugin->getTitle() );
+ SettingsPage page( panel.model, document, category, plugin->getId() );
+ plugin->registerSettings( page );
+ const std::string path = document->path();
+ addAction( panel,
+ { plugin->getId() + ".edit-json", category,
+ mApp->i18n( "edit_settings_json", "Edit Settings JSON" ),
+ mApp->i18n( "edit_settings_json_desc",
+ "Open the complete plugin configuration file." ) },
+ mApp->i18n( "open_file_ellipsis", "Open File..." ), [this, &panel, path] {
+ auto* window = panel.window;
+ window->runOnMainThread( [this, window, path] {
+ window->closeWindow();
+ mApp->getUISceneNode()->runOnMainThread(
+ [this, path] { mApp->focusOrLoadFile( path ); } );
+ } );
+ } );
+ panel.documents.emplace_back( std::move( document ) );
+ } );
+}
+
void SettingsPanel::addProjectSettings( PanelState& panel ) {
addCategory( panel, "editor.document", mApp->i18n( "editor", "Editor" ),
mApp->i18n( "document", "Document" ) );
diff --git a/src/tools/ecode/settingspanel.hpp b/src/tools/ecode/settingspanel.hpp
index 79bdb3186..ab415e364 100644
--- a/src/tools/ecode/settingspanel.hpp
+++ b/src/tools/ecode/settingspanel.hpp
@@ -10,6 +10,7 @@
namespace ecode {
class App;
+class SettingsDocument;
class SettingsPanel {
public:
@@ -40,6 +41,7 @@ class SettingsPanel {
std::shared_ptr categoryModel;
UIBindingGroup bindingGroup;
SettingsModel model;
+ std::vector> documents;
std::vector>> categoryItems;
UnorderedMap categoryIds;
UnorderedMap categorySearchText;
@@ -61,42 +63,65 @@ class SettingsPanel {
PanelState mProject;
PanelState& state( Scope scope );
+
void create( Scope scope );
+
void selectCategory( PanelState& state, const std::string& category );
+
void addUserSettings( PanelState& state );
+
+ void addPluginSettings( PanelState& state );
+
void addProjectSettings( PanelState& state );
+
void addCategory( PanelState& state, const std::string& id, const String& parent,
const String& name );
+
void addSubcategoryHeading( PanelState& state, const std::string& category,
const String& name );
+
void setupCategories( PanelState& state );
+
void addBool( PanelState& state, SettingDescriptor binding, bool* value,
std::function apply = {} );
+
void addBool( PanelState& state, SettingDescriptor binding, std::function get,
std::function set );
+
void addChoice( PanelState& state, SettingDescriptor binding,
const std::vector& choices, std::function get,
std::function set,
std::vector choiceDescriptions = {} );
+
void addEditableChoice( PanelState& state, SettingDescriptor binding,
const std::vector& choices, std::function get,
std::function set );
+
void addInteger( PanelState& state, SettingDescriptor binding, int min, int max,
std::function get, std::function set );
+
void addText( PanelState& state, SettingDescriptor binding, std::function get,
std::function set, bool commitOnFocusLoss = false );
+
void addFloat( PanelState& state, SettingDescriptor binding, double min, double max,
double step, std::function get, std::function set );
+
void addAction( PanelState& state, SettingDescriptor binding, const String& buttonText,
std::function action );
+
UIWidget* createRow( PanelState& state, SettingDefinition& setting, SettingView& view,
pugi::xml_node layout );
+
UICheckBox* createBoolControl( PanelState& state, SettingDefinition& setting,
SettingView& view );
+
void materializeCategory( PanelState& state, const std::string& category );
+
void materializeVisibleSettings( PanelState& state, const String& query );
+
void setCategoryEnabled( PanelState& state, const std::string& category, bool enabled,
const std::string& excludedSetting = {} );
+
void filter( PanelState& state );
};