From e119a0deeefd1bec808f308fee02de3e5c99867f Mon Sep 17 00:00:00 2001 From: Billy Date: Thu, 5 Feb 2026 01:50:43 +0530 Subject: [PATCH 1/6] feat: Settings editor sync with `settings.toml` --- config/settings.toml | 34 +++++----- src/biscuit/config.py | 15 +++++ src/biscuit/settings/config.py | 86 ++++++++++++++++++++------ src/biscuit/settings/editor/editor.py | 36 +++++++---- src/biscuit/settings/editor/items.py | 47 ++++++++------ src/biscuit/settings/editor/section.py | 20 +++--- src/biscuit/settings/settings.py | 7 +++ 7 files changed, 173 insertions(+), 72 deletions(-) diff --git a/config/settings.toml b/config/settings.toml index e5996e8e..02bc2851 100644 --- a/config/settings.toml +++ b/config/settings.toml @@ -1,18 +1,20 @@ -theme="default" -font="firacode" -font_size=13 - +theme = "gruvbox_dark" +font = "Fira Code" +font_size = 13 +cursor_style = "line" +auto_save = false +tab_size = 4 [testing] -font_size=14 -tab_size=4 -theme="light" -auto_save=false -auto_closing_pairs=true -auto_closing_delete=true -auto_indent=true -auto_surround=true -cursor_style="line" -word_wrap=false -exclude_dirs=[".git"] -exclude_types=["__pycache__"] +font_size = 14 +tab_size = 4 +theme = "light" +auto_save = false +auto_closing_pairs = true +auto_closing_delete = true +auto_indent = true +auto_surround = true +cursor_style = "line" +word_wrap = false +exclude_dirs = [ ".git",] +exclude_types = [ "__pycache__",] diff --git a/src/biscuit/config.py b/src/biscuit/config.py index c0a6abac..02bbf372 100644 --- a/src/biscuit/config.py +++ b/src/biscuit/config.py @@ -154,7 +154,22 @@ def set_tab_spaces(self, spaces: int) -> None: if e.content and e.content.editable: e.content.text.set_tab_size(spaces) self.statusbar.set_spaces(spaces) + + def refresh_editors(self) -> None: + self.tab_spaces = self.config.tab_size + self.wrap_words = self.config.word_wrap + self.block_cursor = self.config.cursor_style == "block" + self.theme = self.config.theme + for editor in self.editorsmanager.active_editors: + if editor.content and editor.content.editable: + editor.content.text.configure( + tabs=(self.settings.font.measure(" " * self.tab_spaces),), + blockcursor=self.block_cursor, + wrap=tk.WORD if self.wrap_words else tk.NONE, + **self.theme.editors.text + ) + @property def active_workspace(self): return self.workspaces.workspace diff --git a/src/biscuit/settings/config.py b/src/biscuit/settings/config.py index b0a15ca5..396383d7 100644 --- a/src/biscuit/settings/config.py +++ b/src/biscuit/settings/config.py @@ -1,12 +1,9 @@ import os - import toml from .theme.catppuccin_mocha import CatppuccinMocha from .theme.gruvbox_dark import GruvboxDark -# from .theme import Dark, Light, Theme - class Config: """Loads and manages configurations for biscuit.""" @@ -14,12 +11,9 @@ class Config: def __init__(self, master) -> None: self.base = master.base - self.theme = GruvboxDark() - self.font = ("Fira Code", 12) - self.uifont = ("Fira Code", 10) - - self.auto_save_enabled = False - self.auto_save_timer_ms = 10000 + self.config_path = self.get_config_path("settings.toml") + self.data = {} + self.load_data() def get_config_path(self, relative_path: str) -> str: """Get the absolute path to the resource @@ -27,15 +21,71 @@ def get_config_path(self, relative_path: str) -> str: Args: relative_path (str): path relative to the config directory""" - return os.path.join(self.base.configdir, relative_path) + path = os.path.join(self.base.configdir, relative_path) + if not os.path.exists(path): + # fallback to the default config in the repo + path = os.path.join(self.base.parentdir, "config", relative_path) + + return path - def load_config(self) -> dict: - with open(self.get_config_path("settings.toml"), "r") as settingsfile: - config = toml.load(settingsfile) + def load_data(self) -> None: + """Load configurations from the config file.""" + if os.path.exists(self.config_path): + with open(self.config_path, "r") as configfile: + self.data = toml.load(configfile) + else: + self.data = {} - return config + self.setup_properties() - def load_data(self) -> None: - # TODO testing - self.theme = GruvboxDark() - self.font = (self.config["font"], self.config["font_size"]) + def setup_properties(self) -> None: + """Setup properties based on the loaded data.""" + # TODO add more properties + # Editor + self.font = (self.get_value("font", "Fira Code"), self.get_value("font_size", 12)) + self.uifont = (self.get_value("uifont", "Fira Code"), self.get_value("uifont_size", 10)) + + # Theme + theme_name = self.get_value("theme", "dark") + from .theme import VSCodeDark, VSCodeLight + if theme_name == "dark": + self.theme = VSCodeDark() + elif theme_name == "light": + self.theme = VSCodeLight() + elif theme_name == "gruvbox_dark": + self.theme = GruvboxDark() + elif theme_name == "catppuccin_mocha": + self.theme = CatppuccinMocha() + else: + self.theme = VSCodeDark() + + # Text Editor + self.auto_save_enabled = self.get_value("auto_save", False) + self.auto_closing_pairs = self.get_value("auto_closing_pairs", True) + self.auto_closing_delete = self.get_value("auto_closing_delete", True) + self.auto_indent = self.get_value("auto_indent", True) + self.auto_surround = self.get_value("auto_surround", True) + self.word_wrap = self.get_value("word_wrap", False) + self.tab_size = self.get_value("tab_size", 4) + self.cursor_style = self.get_value("cursor_style", "line") + + def get_value(self, key: str, default: any) -> any: + """Get a value from the config data.""" + return self.data.get(key, default) + + def set_value(self, key: str, value: any) -> None: + """Set a value in the config data and save it.""" + self.data[key] = value + self.save() + self.setup_properties() + + self.base.refresh_editors() + if "font" in key: + self.base.settings.update_font() + + def save(self) -> None: + """Save the current config data to the config file.""" + # ensure config directory exists + os.makedirs(os.path.dirname(self.config_path), exist_ok=True) + with open(self.config_path, "w") as configfile: + toml.dump(self.data, configfile) diff --git a/src/biscuit/settings/editor/editor.py b/src/biscuit/settings/editor/editor.py index 42b3b09a..b8613518 100644 --- a/src/biscuit/settings/editor/editor.py +++ b/src/biscuit/settings/editor/editor.py @@ -51,21 +51,35 @@ def add_commonly_used(self): commonly_used = self.add_section(f"Commonly Used") - commonly_used.add_dropdown("Color Theme", ("dark", "light")) - commonly_used.add_intvalue("Font Size", 14) - commonly_used.add_stringvalue("Font Family", "Consolas") - commonly_used.add_intvalue("Tab Size", 4) + commonly_used.add_dropdown("Color Theme", ("dark", "light", "gruvbox_dark", "catppuccin_mocha"), + self.base.config.get_value("theme", "dark"), + lambda v: self.base.config.set_value("theme", v)) + commonly_used.add_intvalue("Font Size", self.base.config.get_value("font_size", 12), + lambda v: self.base.config.set_value("font_size", int(v) if v else 12)) + commonly_used.add_stringvalue("Font Family", self.base.config.get_value("font", "Fira Code"), + lambda v: self.base.config.set_value("font", v)) + commonly_used.add_intvalue("Tab Size", self.base.config.get_value("tab_size", 4), + lambda v: self.base.config.set_value("tab_size", int(v) if v else 4)) def add_text_editor(self): """Add text editor settings to the settings editor""" - commonly_used = self.add_section(f"Text Editor") - commonly_used.add_checkbox("Auto Save", False) - commonly_used.add_checkbox("Auto Closing Pairs", True) - commonly_used.add_checkbox("Auto Closing Delete", True) - commonly_used.add_checkbox("Auto Indent", True) - commonly_used.add_checkbox("Auto Surround", True) - commonly_used.add_checkbox("Word Wrap", False) + text_editor = self.add_section(f"Text Editor") + text_editor.add_checkbox("Auto Save", self.base.config.get_value("auto_save", False), + lambda v: self.base.config.set_value("auto_save", v)) + text_editor.add_checkbox("Auto Closing Pairs", self.base.config.get_value("auto_closing_pairs", True), + lambda v: self.base.config.set_value("auto_closing_pairs", v)) + text_editor.add_checkbox("Auto Closing Delete", self.base.config.get_value("auto_closing_delete", True), + lambda v: self.base.config.set_value("auto_closing_delete", v)) + text_editor.add_checkbox("Auto Indent", self.base.config.get_value("auto_indent", True), + lambda v: self.base.config.set_value("auto_indent", v)) + text_editor.add_checkbox("Auto Surround", self.base.config.get_value("auto_surround", True), + lambda v: self.base.config.set_value("auto_surround", v)) + text_editor.add_checkbox("Word Wrap", self.base.config.get_value("word_wrap", False), + lambda v: self.base.config.set_value("word_wrap", v)) + text_editor.add_dropdown("Cursor Style", ("line", "block", "underline"), + self.base.config.get_value("cursor_style", "line"), + lambda v: self.base.config.set_value("cursor_style", v)) def add_section(self, name: str) -> Section: """Add a section to the settings editor diff --git a/src/biscuit/settings/editor/items.py b/src/biscuit/settings/editor/items.py index 7a019a69..7058e2dd 100644 --- a/src/biscuit/settings/editor/items.py +++ b/src/biscuit/settings/editor/items.py @@ -5,10 +5,11 @@ class Item(Frame): - def __init__(self, master, name="Example", *args, **kwargs) -> None: + def __init__(self, master, name="Example", callback=None, *args, **kwargs) -> None: super().__init__(master, *args, **kwargs) self.name = name + self.callback = callback self.description = None # TODO add descriptions self.bg, self.fg, self.highlightbg, _ = self.base.theme.editors.section.values() @@ -24,16 +25,9 @@ def __init__(self, master, name="Example", *args, **kwargs) -> None: ) self.lbl.pack(fill=tk.X, expand=True) - # self.bind("", self.hoverin) - # self.bind("", self.hoveroff) - - # def hoverin(self, *_): - # self.config(bg=self.highlightbg) - # self.lbl.config(bg=self.highlightbg) - - # def hoveroff(self, *_): - # self.config(bg=self.bg) - # self.lbl.config(bg=self.bg) + def change(self, *_) -> None: + if self.callback: + self.callback(self.value) class DropdownItem(Item): @@ -43,12 +37,21 @@ def __init__( name="Example", options=["True", "False"], default=0, + callback=None, *args, **kwargs ) -> None: - super().__init__(master, name, *args, **kwargs) + super().__init__(master, name, callback, *args, **kwargs) + + if isinstance(default, str): + try: + default = options.index(default) + except ValueError: + default = 0 self.var = tk.StringVar(self, value=options[default]) + self.var.trace_add("write", self.change) + m = ttk.OptionMenu(self, self.var, options[default], *options) m.config(width=30) m.pack(side=tk.LEFT) @@ -59,8 +62,8 @@ def value(self) -> str: class IntegerItem(Item): - def __init__(self, master, name="Example", default="0", *args, **kwargs) -> None: - super().__init__(master, name, *args, **kwargs) + def __init__(self, master, name="Example", default="0", callback=None, *args, **kwargs) -> None: + super().__init__(master, name, callback, *args, **kwargs) self.base.register(self.validate) self.entry = ttk.Entry( @@ -70,8 +73,10 @@ def __init__(self, master, name="Example", default="0", *args, **kwargs) -> None validate="key", validatecommand=(self.register(self.validate), "%P"), ) - self.entry.insert(0, default) + self.entry.insert(0, str(default)) self.entry.pack(side=tk.LEFT) + self.entry.bind("", self.change) + self.entry.bind("", self.change) def validate(self, value) -> None: return bool(value.isdigit() or value == "") @@ -83,13 +88,15 @@ def value(self) -> str: class StringItem(Item): def __init__( - self, master, name="Example", default="placeholder", *args, **kwargs + self, master, name="Example", default="placeholder", callback=None, *args, **kwargs ) -> None: - super().__init__(master, name, *args, **kwargs) + super().__init__(master, name, callback, *args, **kwargs) self.entry = ttk.Entry(self, font=self.base.settings.uifont, width=30) self.entry.insert(tk.END, default) self.entry.pack(side=tk.LEFT) + self.entry.bind("", self.change) + self.entry.bind("", self.change) @property def value(self) -> str: @@ -97,10 +104,12 @@ def value(self) -> str: class CheckboxItem(Item): - def __init__(self, master, name="Example", default=True, *args, **kwargs) -> None: - super().__init__(master, name, *args, **kwargs) + def __init__(self, master, name="Example", default=True, callback=None, *args, **kwargs) -> None: + super().__init__(master, name, callback, *args, **kwargs) self.var = tk.BooleanVar(self, value=default) + self.var.trace_add("write", self.change) + ttk.Checkbutton(self, text=name, variable=self.var, cursor="hand2").pack( fill=tk.X, anchor=tk.W ) diff --git a/src/biscuit/settings/editor/section.py b/src/biscuit/settings/editor/section.py index b4fc92a9..22939779 100644 --- a/src/biscuit/settings/editor/section.py +++ b/src/biscuit/settings/editor/section.py @@ -32,7 +32,7 @@ def __init__(self, master, title="", *args, **kwargs) -> None: ).pack(fill=tk.X, expand=True) def add_dropdown( - self, name="Example", options=["True", "False"], default=0 + self, name="Example", options=["True", "False"], default=0, callback=None ) -> None: """Add a dropdown item to the section @@ -40,44 +40,48 @@ def add_dropdown( name (str, optional): name of the dropdown. Defaults to "Example". options (list, optional): list of options for the dropdown. Defaults to ["True", "False"]. default (int, optional): default value of the dropdown. Defaults to 0. + callback (function, optional): callback function to be called when the value is changed. """ - dropdown = DropdownItem(self, name, options, default) + dropdown = DropdownItem(self, name, options, default, callback) dropdown.pack(fill=tk.X, expand=True) self.items.append(dropdown) - def add_stringvalue(self, name="Example", default="placeholder") -> None: + def add_stringvalue(self, name="Example", default="placeholder", callback=None) -> None: """Add a string text box item to the section Args: name (str, optional): name of the string. Defaults to "Example". default (str, optional): default value of the string. Defaults to "placeholder". + callback (function, optional): callback function to be called when the value is changed. """ - string = StringItem(self, name, default) + string = StringItem(self, name, default, callback) string.pack(fill=tk.X, expand=True) self.items.append(string) - def add_intvalue(self, name="Example", default="0") -> None: + def add_intvalue(self, name="Example", default="0", callback=None) -> None: """Add an integer text box item to the section Args: name (str, optional): name of the integer. Defaults to "Example". default (int, optional): default value of the integer. Defaults to "0". + callback (function, optional): callback function to be called when the value is changed. """ - int = IntegerItem(self, name, default) + int = IntegerItem(self, name, default, callback) int.pack(fill=tk.X, expand=True) self.items.append(int) - def add_checkbox(self, name="Example", default=True) -> None: + def add_checkbox(self, name="Example", default=True, callback=None) -> None: """Add a checkbox item to the section Args: name (str, optional): name of the checkbox. Defaults to "Example". default (bool, optional): default value of the checkbox. Defaults to True. + callback (function, optional): callback function to be called when the value is changed. """ - dropdown = CheckboxItem(self, name, default) + dropdown = CheckboxItem(self, name, default, callback) dropdown.pack(fill=tk.X, expand=True) self.items.append(dropdown) diff --git a/src/biscuit/settings/settings.py b/src/biscuit/settings/settings.py index b3987112..ef5b0960 100644 --- a/src/biscuit/settings/settings.py +++ b/src/biscuit/settings/settings.py @@ -105,6 +105,13 @@ def setup_font(self) -> None: family=self.config.uifont[0], size=self.config.uifont[1], weight="bold" ) + def update_font(self) -> None: + self.font.configure(family=self.config.font[0], size=self.config.font[1]) + self.font_bold.configure(family=self.config.font[0], size=self.config.font[1], weight="bold") + self.autocomplete_font.configure(family=self.config.font[0], size=self.config.font[1] - 1) + self.uifont.configure(family=self.config.uifont[0], size=self.config.uifont[1]) + self.uifont_bold.configure(family=self.config.uifont[0], size=self.config.uifont[1], weight="bold") + def late_setup(self) -> None: """Configurations that require full initialization of editor""" From ca1d1ebdbcc5aca6b9def14ef60968ac54ebd2fb Mon Sep 17 00:00:00 2001 From: Billy Date: Thu, 5 Feb 2026 01:53:16 +0530 Subject: [PATCH 2/6] fix: Tkinter import missing --- config/settings.toml | 2 +- src/biscuit/config.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/settings.toml b/config/settings.toml index 02bc2851..a864b077 100644 --- a/config/settings.toml +++ b/config/settings.toml @@ -1,4 +1,4 @@ -theme = "gruvbox_dark" +theme = "dark" font = "Fira Code" font_size = 13 cursor_style = "line" diff --git a/src/biscuit/config.py b/src/biscuit/config.py index 02bbf372..3e9ff5f6 100644 --- a/src/biscuit/config.py +++ b/src/biscuit/config.py @@ -3,6 +3,7 @@ import os import sys import typing +import tkinter as tk from pathlib import Path from biscuit.layout.statusbar.statusbar import Statusbar From 6ea653199144068f3c5fd871d2f40012b46e7f99 Mon Sep 17 00:00:00 2001 From: Billy Date: Thu, 5 Feb 2026 02:33:01 +0530 Subject: [PATCH 3/6] feat: More settings added (relative line numbers, ui font config. Fixed settings search. --- src/biscuit/common/ui/scrollableframe.py | 6 +++ src/biscuit/config.py | 3 ++ src/biscuit/settings/config.py | 1 + src/biscuit/settings/editor/editor.py | 62 +++++++++++++++++++++--- src/biscuit/settings/editor/searchbar.py | 19 +------- 5 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/biscuit/common/ui/scrollableframe.py b/src/biscuit/common/ui/scrollableframe.py index a90bc64c..5ce94d45 100644 --- a/src/biscuit/common/ui/scrollableframe.py +++ b/src/biscuit/common/ui/scrollableframe.py @@ -45,6 +45,12 @@ def _configure_canvas(self, event) -> None: def _on_mousewheel(self, event) -> None: self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") + def bind_scroll(self, widget): + widget.bind("", self._on_mousewheel) + for child in widget.winfo_children(): + self.bind_scroll(child) + def add(self, content, *args, **kwargs) -> None: content.pack(in_=self.content, *args, **kwargs) self.items.append(content) + self.bind_scroll(content) diff --git a/src/biscuit/config.py b/src/biscuit/config.py index 3e9ff5f6..816bb3f6 100644 --- a/src/biscuit/config.py +++ b/src/biscuit/config.py @@ -160,6 +160,7 @@ def refresh_editors(self) -> None: self.tab_spaces = self.config.tab_size self.wrap_words = self.config.word_wrap self.block_cursor = self.config.cursor_style == "block" + self.relative_line_numbers = self.config.relative_line_numbers self.theme = self.config.theme for editor in self.editorsmanager.active_editors: @@ -170,6 +171,8 @@ def refresh_editors(self) -> None: wrap=tk.WORD if self.wrap_words else tk.NONE, **self.theme.editors.text ) + editor.content.text.relative_line_numbers = self.relative_line_numbers + editor.content.linenumbers.redraw() @property def active_workspace(self): diff --git a/src/biscuit/settings/config.py b/src/biscuit/settings/config.py index 396383d7..dd1cbbd1 100644 --- a/src/biscuit/settings/config.py +++ b/src/biscuit/settings/config.py @@ -68,6 +68,7 @@ def setup_properties(self) -> None: self.word_wrap = self.get_value("word_wrap", False) self.tab_size = self.get_value("tab_size", 4) self.cursor_style = self.get_value("cursor_style", "line") + self.relative_line_numbers = self.get_value("relative_line_numbers", False) def get_value(self, key: str, default: any) -> any: """Get a value from the config data.""" diff --git a/src/biscuit/settings/editor/editor.py b/src/biscuit/settings/editor/editor.py index b8613518..173c3f54 100644 --- a/src/biscuit/settings/editor/editor.py +++ b/src/biscuit/settings/editor/editor.py @@ -56,6 +56,8 @@ def add_commonly_used(self): lambda v: self.base.config.set_value("theme", v)) commonly_used.add_intvalue("Font Size", self.base.config.get_value("font_size", 12), lambda v: self.base.config.set_value("font_size", int(v) if v else 12)) + commonly_used.add_intvalue("UI Font Size", self.base.config.get_value("uifont_size", 10), + lambda v: self.base.config.set_value("uifont_size", int(v) if v else 10)) commonly_used.add_stringvalue("Font Family", self.base.config.get_value("font", "Fira Code"), lambda v: self.base.config.set_value("font", v)) commonly_used.add_intvalue("Tab Size", self.base.config.get_value("tab_size", 4), @@ -65,6 +67,8 @@ def add_text_editor(self): """Add text editor settings to the settings editor""" text_editor = self.add_section(f"Text Editor") + text_editor.add_checkbox("Relative Line Numbers", self.base.config.get_value("relative_line_numbers", False), + lambda v: self.base.config.set_value("relative_line_numbers", v)) text_editor.add_checkbox("Auto Save", self.base.config.get_value("auto_save", False), lambda v: self.base.config.set_value("auto_save", v)) text_editor.add_checkbox("Auto Closing Pairs", self.base.config.get_value("auto_closing_pairs", True), @@ -81,6 +85,15 @@ def add_text_editor(self): self.base.config.get_value("cursor_style", "line"), lambda v: self.base.config.set_value("cursor_style", v)) + def add_section(self, name: str) -> Section: + """Add a section to the settings editor + + Args: + name (str): name of the section + + Returns: + Section: section to add items to""" + def add_section(self, name: str) -> Section: """Add a section to the settings editor @@ -94,21 +107,56 @@ def add_section(self, name: str) -> Section: section.pack(fill=tk.X, expand=True) self.sections.append(section) - shortcut = Button(self.tree, name, anchor=tk.W) + shortcut = Button(self.tree, name, anchor=tk.W, command=lambda: self.scroll_to_section(section)) shortcut.pack(fill=tk.X) shortcut.config(**self.base.theme.editors.button) return section - def show_result(self, items): + def scroll_to_section(self, section: Section): + """Scrolls to the specified section.""" + # Using update_idletasks to ensure geometry is up to date + self.update_idletasks() + + # Calculate the y-position of the section relative to the content frame + y_pos = section.winfo_y() + + # Calculate the total height of the scrollable content + total_height = self.container.content.winfo_height() + + # Avoid division by zero + if total_height == 0: + return + + # Calculate the scroll fraction (0.0 to 1.0) + fraction = y_pos / total_height + + # Scroll the canvas + self.container.canvas.yview_moveto(fraction) + + def show_result(self, term: str): """Show the search results in the settings editor Args: - items (list): list of items to show in the settings editor""" - - if not any(items): - return self.show_no_results() + term (str): the search term""" + + term = term.lower() + + for section in self.sections: + visible_items = 0 + for item in section.items: + if term in item.name.lower(): + item.pack(fill=tk.X, expand=True) + visible_items += 1 + else: + item.pack_forget() + + if visible_items > 0: + section.pack(fill=tk.X, expand=True) + else: + section.pack_forget() def show_no_results(self): """Show no results found message in the settings editor""" - ... + # TODO implement this + pass diff --git a/src/biscuit/settings/editor/searchbar.py b/src/biscuit/settings/editor/searchbar.py index b51b29cc..14b0a440 100644 --- a/src/biscuit/settings/editor/searchbar.py +++ b/src/biscuit/settings/editor/searchbar.py @@ -41,21 +41,4 @@ def get_search_term(self) -> str: def filter(self, *args) -> str: term = self.get_search_term() - return - new = [ - i for i in self.master.active_set if i[0].lower().startswith(term.lower()) - ] - new += [ - i - for i in self.master.active_set - if any( - [ - f.lower() in i[0].lower() - or i[0].lower() in f.lower() - and i not in new - for f in term.lower().split() - ] - ) - ] - - self.master.show_result(new) + self.master.show_result(term) From 946e10c2b3d580d3f32f23b10db1f9a837262e25 Mon Sep 17 00:00:00 2001 From: Billy Date: Thu, 5 Feb 2026 02:33:16 +0530 Subject: [PATCH 4/6] fix: Migrated to toml from sqlite for history/sessions --- src/biscuit/common/fixedstack.py | 27 ++------- src/biscuit/gui.py | 1 - src/biscuit/history.py | 43 +++++++------- src/biscuit/session.py | 81 ++++++++++++--------------- src/biscuit/settings/editor/editor.py | 2 +- 5 files changed, 62 insertions(+), 92 deletions(-) diff --git a/src/biscuit/common/fixedstack.py b/src/biscuit/common/fixedstack.py index 4f30c0eb..e6f72fe9 100644 --- a/src/biscuit/common/fixedstack.py +++ b/src/biscuit/common/fixedstack.py @@ -1,7 +1,6 @@ from __future__ import annotations import os -import sqlite3 class FixedSizeStack: @@ -45,27 +44,13 @@ def __len__(self): def clear(self): self.stack.clear() - def dump_sqlite(self, cursor: sqlite3.Cursor) -> None: - """Dump the stack to the database. + def dump(self) -> None: + """Dump the stack to the file.""" + return self.stack - Args: - cursor (sqlite3.Cursor): the cursor to the database""" - - # print(self.name, self.stack) - cursor.execute(f"DELETE FROM {self.name};") - cursor.executemany( - f"INSERT INTO {self.name} (path) VALUES (?);", - [(item,) for item in self.stack], - ) - - def load_sqlite(self, cursor: sqlite3.Cursor) -> FixedSizeStack: - """Load the stack from the database. - - Args: - cursor (sqlite3.Cursor): the cursor to the database""" - - cursor.execute(f"SELECT path FROM {self.name};") - self.stack = [item[0] for item in cursor.fetchall()] + def load(self, data) -> FixedSizeStack: + """Load the stack from the file.""" + self.stack = data return self def open_item(self, item): diff --git a/src/biscuit/gui.py b/src/biscuit/gui.py index eb1bcbf6..66cabe55 100644 --- a/src/biscuit/gui.py +++ b/src/biscuit/gui.py @@ -1,5 +1,4 @@ import platform -import sqlite3 from tkinterDnD import Tk diff --git a/src/biscuit/history.py b/src/biscuit/history.py index 35b2d2fb..bf65c1f0 100644 --- a/src/biscuit/history.py +++ b/src/biscuit/history.py @@ -1,42 +1,34 @@ from __future__ import annotations import os -import sqlite3 +import toml import typing - from .common import ActionSet, FixedSizeStack if typing.TYPE_CHECKING: from . import App - class HistoryManager: """Manages the history of opened files and folders. Manages the history of opened files and folders. - Uses an sqlite3 database to store the history. + Uses a TOML file to store the history. """ def __init__(self, base: App) -> None: self.base = base - self.path = self.base.datadir / "history.db" - - self.db = sqlite3.connect(self.path) - self.cursor = self.db.cursor() + self.path = self.base.datadir / "history.toml" + self.history = {} - self.cursor.executescript( - """ - CREATE TABLE IF NOT EXISTS file_history (path TEXT NOT NULL); - CREATE TABLE IF NOT EXISTS folder_history (path TEXT NOT NULL); - """ - ) + if self.path.exists(): + try: + self.history = toml.load(self.path) + except Exception as e: + self.base.logger.error(f"History load failed: {e}") + self.history = {} - self.file_history = FixedSizeStack(self, "file_history").load_sqlite( - self.cursor - ) - self.folder_history = FixedSizeStack(self, "folder_history").load_sqlite( - self.cursor - ) + self.file_history = FixedSizeStack(self, "file_history").load(self.history.get("file_history", [])) + self.folder_history = FixedSizeStack(self, "folder_history").load(self.history.get("folder_history", [])) def generate_actionsets(self) -> None: self.base.palette.register_actionset( @@ -53,9 +45,14 @@ def register_folder_history(self, path: str) -> None: self.folder_history.push(path) def dump(self) -> None: - self.file_history.dump_sqlite(self.cursor) - self.folder_history.dump_sqlite(self.cursor) - self.db.commit() + try: + with open(self.path, 'w') as f: + toml.dump({ + "file_history": self.file_history.dump(), + "folder_history": self.folder_history.dump(), + }, f) + except Exception as e: + self.base.logger.error(f"History save failed: {e}") def clear_history(self) -> None: self.file_history.clear() diff --git a/src/biscuit/session.py b/src/biscuit/session.py index 73ce45c7..287dd024 100644 --- a/src/biscuit/session.py +++ b/src/biscuit/session.py @@ -2,65 +2,54 @@ from __future__ import annotations -import sqlite3 as sq -import typing - -if typing.TYPE_CHECKING: - from biscuit import App - +import toml class SessionManager: def __init__(self, base: App): self.base = base + self.session_path = self.base.datadir / "session.toml" + self.session = {} - # Initialize the session database connection - self.base_dir = self.base.datadir - self.session_db_path = self.base.datadir / "session.db" - self.db = sq.connect(self.session_db_path) - self.cursor = self.db.cursor() - - # Ensure the session table is created - self._create_session_table() - - def _create_session_table(self): - self.cursor.executescript( - """ - CREATE TABLE IF NOT EXISTS session ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - file_path TEXT, - folder_path TEXT - ); - """ - ) + if self.session_path.exists(): + try: + self.session = toml.load(self.session_path) + except Exception as e: + self.base.notifications.error(f"Failed to load session: {e}") + self.session = {} def restore_session(self): - opened_files = [] - active_directory = "" + if not self.session: + return - self.cursor.execute("SELECT * FROM session") - for row in self.cursor.fetchall(): - if row[1]: - opened_files.append(row[1]) - elif row[2]: - active_directory = row[2] + active_directory = self.session.get("active_directory") + opened_files = self.session.get("opened_files", []) - self.base.open_directory(active_directory) + if active_directory: + self.base.open_directory(active_directory) + self.base.open_files(opened_files) def clear_session(self): - self.cursor.execute("DELETE FROM session") + self.session = {} + if self.session_path.exists(): + try: + # Create empty file or just empty dict + with open(self.session_path, 'w') as f: + toml.dump({}, f) + except Exception as e: + self.base.logger.error(f"Failed to clear session: {e}") def save_session(self, opened_files, active_directory): - for file_path in opened_files: - self.cursor.execute( - "INSERT INTO session (file_path) VALUES (?)", (file_path,) - ) - - self.cursor.execute( - "INSERT INTO session (folder_path) VALUES (?)", (active_directory,) - ) - - self.db.commit() + self.session = { + "active_directory": active_directory, + "opened_files": opened_files + } + + try: + with open(self.session_path, 'w') as f: + toml.dump(self.session, f) + except Exception as e: + self.base.logger.error(f"Failed to save session: {e}") def close(self): - self.db.close() + pass diff --git a/src/biscuit/settings/editor/editor.py b/src/biscuit/settings/editor/editor.py index 173c3f54..e9e4c0e1 100644 --- a/src/biscuit/settings/editor/editor.py +++ b/src/biscuit/settings/editor/editor.py @@ -107,7 +107,7 @@ def add_section(self, name: str) -> Section: section.pack(fill=tk.X, expand=True) self.sections.append(section) - shortcut = Button(self.tree, name, anchor=tk.W, command=lambda: self.scroll_to_section(section)) + shortcut = Button(self.tree, name, anchor=tk.W, command=lambda *_: self.scroll_to_section(section)) shortcut.pack(fill=tk.X) shortcut.config(**self.base.theme.editors.button) From a80840146b8298f166e9ff2a6ddc87aa362d31da Mon Sep 17 00:00:00 2001 From: Billy Date: Thu, 5 Feb 2026 02:46:22 +0530 Subject: [PATCH 5/6] feat: More settings (minimap, breadcrumbs, line numbers, indent guides) --- src/biscuit/config.py | 12 ++++++++++++ src/biscuit/settings/config.py | 6 ++++++ src/biscuit/settings/editor/editor.py | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/src/biscuit/config.py b/src/biscuit/config.py index 816bb3f6..2befb0a5 100644 --- a/src/biscuit/config.py +++ b/src/biscuit/config.py @@ -161,6 +161,8 @@ def refresh_editors(self) -> None: self.wrap_words = self.config.word_wrap self.block_cursor = self.config.cursor_style == "block" self.relative_line_numbers = self.config.relative_line_numbers + self.show_minimap = self.config.show_minimap + self.show_linenumbers = self.config.show_linenumbers self.theme = self.config.theme for editor in self.editorsmanager.active_editors: @@ -174,6 +176,16 @@ def refresh_editors(self) -> None: editor.content.text.relative_line_numbers = self.relative_line_numbers editor.content.linenumbers.redraw() + if self.show_minimap: + editor.content.minimap.grid() + else: + editor.content.minimap.grid_remove() + + if self.show_linenumbers: + editor.content.linenumbers.grid() + else: + editor.content.linenumbers.grid_remove() + @property def active_workspace(self): return self.workspaces.workspace diff --git a/src/biscuit/settings/config.py b/src/biscuit/settings/config.py index dd1cbbd1..46873e0d 100644 --- a/src/biscuit/settings/config.py +++ b/src/biscuit/settings/config.py @@ -69,6 +69,12 @@ def setup_properties(self) -> None: self.tab_size = self.get_value("tab_size", 4) self.cursor_style = self.get_value("cursor_style", "line") self.relative_line_numbers = self.get_value("relative_line_numbers", False) + + # Display + self.show_minimap = self.get_value("show_minimap", True) + self.show_breadcrumbs = self.get_value("show_breadcrumbs", True) + self.show_linenumbers = self.get_value("show_line_numbers", True) + self.render_indent_guides = self.get_value("render_indent_guides", True) def get_value(self, key: str, default: any) -> any: """Get a value from the config data.""" diff --git a/src/biscuit/settings/editor/editor.py b/src/biscuit/settings/editor/editor.py index e9e4c0e1..cb5092ed 100644 --- a/src/biscuit/settings/editor/editor.py +++ b/src/biscuit/settings/editor/editor.py @@ -85,6 +85,15 @@ def add_text_editor(self): self.base.config.get_value("cursor_style", "line"), lambda v: self.base.config.set_value("cursor_style", v)) + text_editor.add_checkbox("Minimap", self.base.config.get_value("show_minimap", True), + lambda v: self.base.config.set_value("show_minimap", v)) + text_editor.add_checkbox("Breadcrumbs", self.base.config.get_value("show_breadcrumbs", True), + lambda v: self.base.config.set_value("show_breadcrumbs", v)) + text_editor.add_checkbox("Line Numbers", self.base.config.get_value("show_line_numbers", True), + lambda v: self.base.config.set_value("show_line_numbers", v)) + text_editor.add_checkbox("Render Indent Guides", self.base.config.get_value("render_indent_guides", True), + lambda v: self.base.config.set_value("render_indent_guides", v)) + def add_section(self, name: str) -> Section: """Add a section to the settings editor From 588696b3e0cf6b5f3e69a6100197bd3a661454f9 Mon Sep 17 00:00:00 2001 From: Billy Date: Thu, 5 Feb 2026 02:52:05 +0530 Subject: [PATCH 6/6] fix: Ensure indent guides are toggling --- config/settings.toml | 4 ++++ src/biscuit/config.py | 14 +++++++++++++- src/biscuit/editor/text/text.py | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/config/settings.toml b/config/settings.toml index a864b077..c0f0413d 100644 --- a/config/settings.toml +++ b/config/settings.toml @@ -4,6 +4,10 @@ font_size = 13 cursor_style = "line" auto_save = false tab_size = 4 +show_minimap = false +render_indent_guides = false +show_breadcrumbs = false +show_line_numbers = false [testing] font_size = 14 diff --git a/src/biscuit/config.py b/src/biscuit/config.py index 2befb0a5..0b66e786 100644 --- a/src/biscuit/config.py +++ b/src/biscuit/config.py @@ -185,7 +185,19 @@ def refresh_editors(self) -> None: editor.content.linenumbers.grid() else: editor.content.linenumbers.grid_remove() - + + if self.config.show_breadcrumbs: + self.editorsmanager.editorsbar.show_breadcrumbs() + else: + self.editorsmanager.editorsbar.hide_breadcrumbs() + + # Update indent guides for all active text editors + # This requires manually triggering an update/refresh in the text widget + # providing the render_indent_guides flag is used in update_indent_guides + for editor in self.editorsmanager.active_editors: + if editor.content and editor.content.editable: + editor.content.text.refresh() + @property def active_workspace(self): return self.workspaces.workspace diff --git a/src/biscuit/editor/text/text.py b/src/biscuit/editor/text/text.py index 1b619df8..f4bf067f 100644 --- a/src/biscuit/editor/text/text.py +++ b/src/biscuit/editor/text/text.py @@ -303,7 +303,7 @@ def diagnostic_hover(self, severity: int) -> str: self.base.diagnostic.show(self, start, message, severity) def update_indent_guides(self) -> None: - if self.minimalist: + if self.minimalist or not self.base.config.render_indent_guides: return self.tag_remove("indent_guide", "1.0", "end")