From 94c2a72c4187314d8e0748c0a588c8523f909a7b Mon Sep 17 00:00:00 2001 From: SpookyDervish <78246495+SpookyDervish@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:45:30 +1100 Subject: [PATCH] plugin defined settings!!!! --- plugin_loader.py | 15 +++-- plugins/test/lua/main.lua | 14 +++++ plugins/test/plugin.json | 6 ++ settings.py | 114 +++++++++++++++++++++++++++----------- settings_store.py | 46 ++++++++++++++- 5 files changed, 156 insertions(+), 39 deletions(-) create mode 100644 plugins/test/lua/main.lua create mode 100644 plugins/test/plugin.json diff --git a/plugin_loader.py b/plugin_loader.py index 67122ce..38c2ea0 100644 --- a/plugin_loader.py +++ b/plugin_loader.py @@ -1,7 +1,8 @@ from textual_window import Window from textual.widgets import RichLog, Button -from textual import work, on +from textual import work import textual.widgets +from typing import Any from traceback import format_exception from lupa import lua51 @@ -68,6 +69,9 @@ class PluginLoader(Window): ) #self.app.mount(new_window) return new_window + + def create_setting(self, section_name: str, option_name: str, description: str, option_type: str, default_value: Any, on_changed_func = None): + self.app.config_handler.define_plugin_setting(section_name, option_name, description, option_type, default_value, on_changed_func) # endregion @@ -83,8 +87,6 @@ class PluginLoader(Window): lua_runtime_stuff = { "ui": { "notify": self.fake_notify, - #"createSidebarButton": self.create_sidebar_button, - #"setTheme": self.set_theme, "runAction": self.fake_run_action, "addBind": self.add_bind, "createAction": self.create_action, @@ -93,7 +95,10 @@ class PluginLoader(Window): "app": self.app, "onMessage": self.run_on_message }, - "config": self.app.config_handler + "config": { + "get": self.app.config_handler.get, + "defineSetting": self.create_setting + } } log.write("[b]Finding plugins...[/]") @@ -190,7 +195,7 @@ class PluginLoader(Window): log.write("\n[b]Done loading plugins![/]") if no_errors and int(self.app.config_handler.get("plugins", "log_timeout")) != -1: - log.write("[d]Window will automatically close in 10 seconds.[/]") + log.write(f'[d]Window will automatically close in {self.app.config_handler.get("plugins", "log_timeout")} seconds.[/]') await asyncio.sleep(int(self.app.config_handler.get("plugins", "log_timeout"))) self.close_window() diff --git a/plugins/test/lua/main.lua b/plugins/test/lua/main.lua new file mode 100644 index 0000000..bf2430f --- /dev/null +++ b/plugins/test/lua/main.lua @@ -0,0 +1,14 @@ +local plugin = {} + +function test(newValue) + berry.ui.notify(tostring(newValue)) +end + +function plugin.run() + berry.config.defineSetting("Test Plugin", "test setting", "this is a description", "string", "hi, this is the default value", test) + berry.config.defineSetting("Test Plugin", "another setting!", "woah!", "boolean", "1", test) + berry.config.defineSetting("Test Plugin", "integer setting", "i love integers :O", "integer", "123", test) + berry.config.defineSetting("Test Plugin", "float setting", "i love floating point numbers :O", "float", "123.456", test) +end + +return plugin \ No newline at end of file diff --git a/plugins/test/plugin.json b/plugins/test/plugin.json new file mode 100644 index 0000000..8d31c5f --- /dev/null +++ b/plugins/test/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "Test Plugin", + "version": "1.0.0", + "author": "SpookyDervish", + "dependencies": [] +} \ No newline at end of file diff --git a/settings.py b/settings.py index e26b190..6d31ec3 100644 --- a/settings.py +++ b/settings.py @@ -1,6 +1,6 @@ from textual.screen import ModalScreen -from textual.widgets import Label, Select, TabbedContent, TabPane, Switch, Input -from textual.containers import Vertical, HorizontalGroup, VerticalGroup +from textual.widgets import Label, Select, TabbedContent, TabPane, Switch, Input, Rule, Static +from textual.containers import Vertical, HorizontalGroup, VerticalGroup, VerticalScroll from textual.binding import Binding @@ -22,6 +22,15 @@ class SettingsScreen(ModalScreen): padding: 1; } + Rule { + color: $boost; + } + + .thingy { + margin-top: 2; + text-style: bold; + } + .setting { padding: 0 2; content-align: center middle; @@ -69,6 +78,9 @@ class SettingsScreen(ModalScreen): self.notify("Restart for changes to apply.", title="Restart Required", severity="warning") elif event.switch.id == "plugins-log": self.app.config_handler.set("plugins", "log", str(int(event.value))) + elif event.switch.has_class("plugin-option"): + if event.switch.berry_changed_func != None: + event.switch.berry_changed_func(event.switch.value) def on_select_changed(self, event: Select.Changed): if event.select.id == "colour-theme": @@ -80,47 +92,83 @@ class SettingsScreen(ModalScreen): if event.input.id == "log-timeout": self.app.config_handler.set("plugins", "log_timeout", str(event.input.value)) + elif event.input.has_class("plugin-option"): + if event.input.berry_changed_func != None: + event.input.berry_changed_func(event.input.value) def compose(self): with Vertical(id="window") as window: window.border_title = "Settings" with TabbedContent(): with TabPane("Appearance"): - with HorizontalGroup(classes="setting"): - with VerticalGroup(): - yield Label("Colour Theme", classes="setting-name") - yield Label("Colour theme used for the entire Berry app. You can get more themes with plugins!", classes="setting-desc") + with VerticalScroll(): + with HorizontalGroup(classes="setting"): + with VerticalGroup(): + yield Label("Colour Theme", classes="setting-name") + yield Label("Colour theme used for the entire Berry app. You can get more themes with plugins!", classes="setting-desc") - yield Select.from_values( - (theme_name for theme_name in self.app._registered_themes.keys() if theme_name != "textual-ansi"), - allow_blank=False, - id="colour-theme", - value=self.app.theme - ) + yield Select.from_values( + (theme_name for theme_name in self.app._registered_themes.keys() if theme_name != "textual-ansi"), + allow_blank=False, + id="colour-theme", + value=self.app.theme + ) with TabPane("Editor"): - with HorizontalGroup(classes="setting"): - with VerticalGroup(): - yield Label("Word Wrap", classes="setting-name") - yield Label("Enable word wrap in the code editor.", classes="setting-desc") - yield Switch(value=bool(int(self.app.config_handler.get("editor", "word_wrap"))), id="word-wrap") + with VerticalScroll(): + with HorizontalGroup(classes="setting"): + with VerticalGroup(): + yield Label("Word Wrap", classes="setting-name") + yield Label("Enable word wrap in the code editor.", classes="setting-desc") + yield Switch(value=bool(int(self.app.config_handler.get("editor", "word_wrap"))), id="word-wrap") with TabPane("Plugins"): + with VerticalScroll(): + + with HorizontalGroup(classes="setting"): + with VerticalGroup(): + yield Label("Plugins Enabled", classes="setting-name") + yield Label("Enable or disable Lua plugins. This requires a restart.", classes="setting-desc") + yield Switch(value=bool(int(self.app.config_handler.get("plugins", "enabled"))), id="plugins-enabled") - with HorizontalGroup(classes="setting"): - with VerticalGroup(): - yield Label("Plugins Enabled", classes="setting-name") - yield Label("Enable or disable Lua plugins. This requires a restart.", classes="setting-desc") - yield Switch(value=bool(int(self.app.config_handler.get("plugins", "enabled"))), id="plugins-enabled") + with HorizontalGroup(classes="setting"): + with VerticalGroup(): + yield Label("Plugins Log", classes="setting-name") + yield Label("Show the plugin loader log on startup.", classes="setting-desc") + yield Switch(value=bool(int(self.app.config_handler.get("plugins", "log"))), id="plugins-log") + + with HorizontalGroup(classes="setting"): + with VerticalGroup(): + yield Label("Log Timeout", classes="setting-name") + yield Label("How many seconds before the log automatically closes. This gets overriden by the [b]Plugins Log[/] option. The window doesn't automatically close if there was an error. If this is set to -1, the log will not close automatically.", classes="setting-desc") + yield Input(value=self.app.config_handler.get("plugins", "log_timeout"), id="log-timeout", type="integer") + + yield Static("Plugin-defined Settings", classes="thingy") + yield Rule(line_style="thick") - with HorizontalGroup(classes="setting"): - with VerticalGroup(): - yield Label("Plugins Log", classes="setting-name") - yield Label("Show the plugin loader log on startup.", classes="setting-desc") - yield Switch(value=bool(int(self.app.config_handler.get("plugins", "log"))), id="plugins-log") - - with HorizontalGroup(classes="setting"): - with VerticalGroup(): - yield Label("Log Timeout", classes="setting-name") - yield Label("How many seconds before the log automatically closes. This gets overriden by the [b]Plugins Log[/] option. The window doesn't automatically close if there was an error. If this is set to -1, the log will not close automatically.", classes="setting-desc") - yield Input(value=self.app.config_handler.get("plugins", "log_timeout"), id="log-timeout", type="integer") \ No newline at end of file + for plugin_section, plugin_settings in self.app.config_handler.plugin_defined_settings.items(): + yield Static(f"{plugin_section}", classes="thingy") + yield Rule() + + for setting_name, setting in plugin_settings.items(): + with HorizontalGroup(classes="setting"): + with VerticalGroup(): + yield Label(setting_name, classes="setting-name") + yield Label(setting["description"], classes="setting-desc") + + value = self.app.config_handler.get(f"plugin_{plugin_section}", setting["option_name"]) + + new_widget = None + if setting["type"] == bool: + value = bool(int(value)) + new_widget = Switch(value=value) + elif setting["type"] == int: + new_widget = Input(value=value, type="integer", placeholder=setting["default_value"]) + elif setting["type"] == float: + new_widget = Input(value=value, type="number", placeholder=setting["default_value"]) + elif setting["type"] == str: + new_widget = Input(value=value, placeholder=setting["default_value"]) + + new_widget.add_class("plugin-option") + setattr(new_widget, "berry_changed_func", setting['on_changed_func']) + yield new_widget \ No newline at end of file diff --git a/settings_store.py b/settings_store.py index 488ebc9..08b4934 100644 --- a/settings_store.py +++ b/settings_store.py @@ -2,6 +2,7 @@ from textual.app import App from pathlib import Path import os import configparser +from typing import Any class ConfigHandler: @@ -9,8 +10,43 @@ class ConfigHandler: self.app: App = app self.config: configparser.ConfigParser = configparser.ConfigParser() + self.plugin_defined_settings = {} self.config_dir: str = self.ensure_hidden_config_dir() self.load_settings() + + def define_plugin_setting(self, plugin_name: str, option_name: str, description: str, option_type: str, default_value: Any, on_changed_func = None): + actual_type = None + if option_type == "boolean": + actual_type = bool + elif option_type == "string": + actual_type = str + elif option_type == "float": + actual_type = float + elif option_type == "integer": + actual_type = int + else: + raise Exception(f"Invalid type name \"{option_type}\".") + + if plugin_name in self.plugin_defined_settings: + self.plugin_defined_settings[plugin_name][option_name] = { + "option_name": option_name, + "description": description, + "type": actual_type, + "default_value": default_value, + "on_changed_func": on_changed_func + } + else: + self.plugin_defined_settings[plugin_name] = {option_name: { + "option_name": option_name, + "description": description, + "type": actual_type, + "default_value": default_value, + "on_changed_func": on_changed_func + }} + + # user hasnt used this plugin before + if self.get("plugin_" + plugin_name, option_name) == None: + self.set("plugin_" + plugin_name, option_name, default_value) def ensure_hidden_config_dir(self): config_dir = Path.home() / ".berry" @@ -19,10 +55,18 @@ class ConfigHandler: return config_dir def get(self, section: str, option: str): - return self.config.get(section, option) + return self.config.get(section, option, fallback=None) def set(self, section: str, option: str, new_value: str): + if not self.config.has_section(section): + self.config.add_section(section) self.config.set(section, option, new_value) + + if section.startswith("plugin_"): + section = section.removeprefix("plugin_") + if self.plugin_defined_settings[section].get("on_changed_func", None) != None: + self.plugin_defined_settings[section]["on_changed_func"](new_value) + self.write_settings() def apply_settings(self):