From 37bdd7377b8b23d53023197331f4cf2b7d952d21 Mon Sep 17 00:00:00 2001 From: derVedro Date: Tue, 28 Apr 2026 05:01:32 +0200 Subject: [PATCH 1/3] just a proof of concept for various PinProviders --- src/rovr/functions/pins.py | 195 ++++++++++++++++++++++++------------- 1 file changed, 125 insertions(+), 70 deletions(-) diff --git a/src/rovr/functions/pins.py b/src/rovr/functions/pins.py index 4a76a322..dac77f72 100644 --- a/src/rovr/functions/pins.py +++ b/src/rovr/functions/pins.py @@ -5,10 +5,26 @@ from rovr.variables.maps import RovrVars -from .path import dump_exc, normalise +from .path import dump_exc , normalise pins = {} PIN_PATH = path.join(RovrVars.ROVRCONFIG, "pins.json") +_places_providers = {} +_bookmarks_providers = {} + +def _register(name, bucket): + def decorator(cls): + bucket[name] = cls + return cls + return decorator + + +def register_places(name): + return _register(name, _places_providers) + + +def register_bookmarks(name): + return _register(name, _bookmarks_providers) class PinItem(TypedDict): @@ -24,58 +40,38 @@ class PinsDict(TypedDict): "Other added folders" -def load_pins() -> PinsDict: - """ - Load the pinned files from a JSON file in the user's config directory. - Returns: - dict: A dictionary with the default values, and the custom added pins. - Raises: - ValueError: If the config is of the wrong type - """ - # I'm not entirely sure why the pins break when - # pins isn't set global, I can't be bothered for now - # until an issue gets raised in the future - global pins - _pins: PinsDict +class PinProvider(): + @classmethod + def load_pins(cls) -> list[PinItem]: + ... - if not path.exists(PIN_PATH): - _pins = { - "default": [ - {"name": "Home", "path": "$HOME"}, - {"name": "Downloads", "path": "$DOWNLOADS"}, - {"name": "Documents", "path": "$DOCUMENTS"}, - {"name": "Desktop", "path": "$DESKTOP"}, - {"name": "Pictures", "path": "$PICTURES"}, - {"name": "Videos", "path": "$VIDEOS"}, - {"name": "Music", "path": "$MUSIC"}, - ], - "pins": [], - } - else: - try: - with open(PIN_PATH, "r") as f: - loaded = json.load(f) - if not isinstance(loaded, dict): - raise ValueError() - _pins = cast(PinsDict, loaded) - except (IOError, ValueError, json.JSONDecodeError): - # Reset pins on corrupt or something else happened - _pins = { - "default": [ - {"name": "Home", "path": "$HOME"}, - {"name": "Downloads", "path": "$DOWNLOADS"}, - {"name": "Documents", "path": "$DOCUMENTS"}, - {"name": "Desktop", "path": "$DESKTOP"}, - {"name": "Pictures", "path": "$PICTURES"}, - {"name": "Videos", "path": "$VIDEOS"}, - {"name": "Music", "path": "$MUSIC"}, - ], - "pins": [], - } - - # If list died - if "default" not in _pins or not isinstance(_pins["default"], list): - _pins["default"] = [ + @classmethod + def add_pin(cls, pin_name: str, pin_path: str | bytes) -> None: + ... + + @classmethod + def remove_pin(cls, pin_path: str | bytes) -> None: + ... + + @classmethod + def toggle_pin(cls, pin_name: str, pin_path: str) -> None: + ... + + +@register_bookmarks("empty") +@register_places("empty") +class EmptyPinProvider(PinProvider): + @classmethod + def load_pins(cls) -> list[PinItem]: + return [] + + +@register_places("default") +class DefaultPlaces(PinProvider): + + @classmethod + def load_pins(cls) -> list[PinItem]: + return [ {"name": "Home", "path": "$HOME"}, {"name": "Downloads", "path": "$DOWNLOADS"}, {"name": "Documents", "path": "$DOCUMENTS"}, @@ -84,25 +80,84 @@ def load_pins() -> PinsDict: {"name": "Videos", "path": "$VIDEOS"}, {"name": "Music", "path": "$MUSIC"}, ] - if "pins" not in _pins or not isinstance(_pins["pins"], list): - _pins["pins"] = [] - for section_key in ["default", "pins"]: - # again, screw you ty, `section_key` can never be unknown - # but i dont know how to assert that to you - for item in _pins[section_key]: # ty: ignore[invalid-key] - # no i will not use isinstance, ty screams at me - # because of the replace code a few lines below - if type(item) is dict and "path" in item and type(item["path"]) is str: - # Expand variables - for var in RovrVars.slots: - item["path"] = item["path"].replace( - f"${var}", getattr(RovrVars, var) - ) - # Normalize to forward slashes - item["path"] = normalise(str(item["path"])) - pins = _pins - return _pins + +@register_places("rovr") +class RovrPinedPlaces(PinProvider): + + @classmethod + def load_pins(cls) -> list[PinItem]: + try: + places = [] + with open(PIN_PATH, "r") as f: + loaded_pins = cast(PinsDict, json.load(f)) + for place in loaded_pins["default"]: + place["path"]=normalise(_expand_vars(place["path"])) + places.append(place) + except (IOError, ValueError, json.decoder.JSONDecodeError): + places = DefaultPlaces.load_pins() + return places + + +@register_bookmarks("rovr") +class RovrPinedBookmarks(PinProvider): + + @classmethod + def load_pins(cls) -> list[PinItem]: + try: + bookmarks = [] + with open(PIN_PATH, "r") as f: + loaded_pins = cast(PinsDict, json.load(f)) + for bookmark in loaded_pins["pins"]: + bookmark["path"]=normalise(_expand_vars(bookmark["path"])) + bookmarks.append(bookmark) + except (IOError, ValueError, json.decoder.JSONDecodeError): + bookmarks = [] + return bookmarks + + +@register_bookmarks("gtk") +class GTKBookmarks(PinProvider): + @classmethod + def load_pins(cls) -> list[PinItem]: + bookmarks = [] + from pathlib import Path + with open(Path("~/.config/gtk-3.0/bookmarks").expanduser()) as bookmarks_file: + for line in bookmarks_file.readlines(): + try: + path, name = line.strip().split(" ", 1) + bookmarks.append({"name": name, "path": str(Path.from_uri(path))}) + except ValueError: + pass + return bookmarks + + +def _expand_vars(path: str) -> str: + for var in RovrVars.slots: + path = path.replace(f"${var}", getattr(RovrVars, var)) + return path + + +def load_pins(): + + # get the values from user config, not implemented yet + config_places = ["rovr"] + config_bookmarks = ["rovr"] # ["rovr", "gtk"] is possible + + _places = [] + _bookmarks = [] + + for provider in config_places: + _places.extend(_places_providers[provider].load_pins()) + for provider in config_bookmarks: + _bookmarks.extend(_bookmarks_providers[provider].load_pins()) + + return { + "default": _places, + "pins": _bookmarks, + } + + def add_pin(pin_name: str, pin_path: str | bytes) -> None: From 1b75e31fd8e24e3e33918d9af64de3ff7da2dfef Mon Sep 17 00:00:00 2001 From: derVedro Date: Thu, 30 Apr 2026 03:07:16 +0200 Subject: [PATCH 2/3] add config options, add kde bookmarks provider --- src/rovr/config/schema.json | 20 +++++++ src/rovr/functions/pins.py | 104 +++++++++++++++++++++++------------- 2 files changed, 86 insertions(+), 38 deletions(-) diff --git a/src/rovr/config/schema.json b/src/rovr/config/schema.json index 41b1ea73..9a84d653 100644 --- a/src/rovr/config/schema.json +++ b/src/rovr/config/schema.json @@ -227,6 +227,26 @@ "application/x-font-.*": "font" }, "description": "Map MIME type patterns to preview types. Uses regex patterns. Valid preview types: text, image, pdf, archive, folder, resvg, font, remime.\n-> Use 'remime' if you want a more accurate description from file(1)" + }, + "pins_places": { + "type": "array", + "default": ["rovr"], + "description": "Provider for the pinned places in the side pane.", + "minItems": 1, + "items": { + "type": "string", + "enum": ["empty", "default", "rovr"] + } + }, + "pins_bookmarks": { + "type": "array", + "default": ["rovr"], + "description": "Provider for the pinned bookmarks in the side pane.", + "minItems": 1, + "items": { + "type": "string", + "enum": ["empty", "rovr", "gtk", "kde"] + } } } }, diff --git a/src/rovr/functions/pins.py b/src/rovr/functions/pins.py index dac77f72..8431dd3d 100644 --- a/src/rovr/functions/pins.py +++ b/src/rovr/functions/pins.py @@ -1,11 +1,14 @@ import json from copy import deepcopy from os import makedirs, path -from typing import NotRequired, TypedDict, cast +from typing import NotRequired, TypedDict, cast, Protocol +from pathlib import Path +import xml.etree.ElementTree as ET from rovr.variables.maps import RovrVars +from rovr.variables.constants import config -from .path import dump_exc , normalise +from .path import dump_exc, normalise pins = {} PIN_PATH = path.join(RovrVars.ROVRCONFIG, "pins.json") @@ -40,38 +43,34 @@ class PinsDict(TypedDict): "Other added folders" -class PinProvider(): +class PinProvider(Protocol): @classmethod - def load_pins(cls) -> list[PinItem]: - ... + def load_pins(cls) -> list[PinItem]: ... @classmethod - def add_pin(cls, pin_name: str, pin_path: str | bytes) -> None: - ... + def add_pin(cls, pin_name: str, pin_path: str | bytes) -> None: ... @classmethod - def remove_pin(cls, pin_path: str | bytes) -> None: - ... + def remove_pin(cls, pin_path: str | bytes) -> None: ... @classmethod - def toggle_pin(cls, pin_name: str, pin_path: str) -> None: - ... + def toggle_pin(cls, pin_name: str, pin_path: str) -> None: ... @register_bookmarks("empty") @register_places("empty") -class EmptyPinProvider(PinProvider): +class EmptyPinProvider(): @classmethod def load_pins(cls) -> list[PinItem]: return [] @register_places("default") -class DefaultPlaces(PinProvider): +class DefaultPlaces(): @classmethod def load_pins(cls) -> list[PinItem]: - return [ + return _sanitize([ {"name": "Home", "path": "$HOME"}, {"name": "Downloads", "path": "$DOWNLOADS"}, {"name": "Documents", "path": "$DOCUMENTS"}, @@ -79,11 +78,11 @@ def load_pins(cls) -> list[PinItem]: {"name": "Pictures", "path": "$PICTURES"}, {"name": "Videos", "path": "$VIDEOS"}, {"name": "Music", "path": "$MUSIC"}, - ] + ]) @register_places("rovr") -class RovrPinedPlaces(PinProvider): +class RovrPinedPlaces(): @classmethod def load_pins(cls) -> list[PinItem]: @@ -91,16 +90,17 @@ def load_pins(cls) -> list[PinItem]: places = [] with open(PIN_PATH, "r") as f: loaded_pins = cast(PinsDict, json.load(f)) - for place in loaded_pins["default"]: - place["path"]=normalise(_expand_vars(place["path"])) - places.append(place) + # for place in loaded_pins["default"]: + # place["path"]=normalise(_expand_vars(place["path"])) + # places.append(place) + places = _sanitize(loaded_pins["default"]) except (IOError, ValueError, json.decoder.JSONDecodeError): places = DefaultPlaces.load_pins() return places @register_bookmarks("rovr") -class RovrPinedBookmarks(PinProvider): +class RovrPinedBookmarks(): @classmethod def load_pins(cls) -> list[PinItem]: @@ -108,29 +108,56 @@ def load_pins(cls) -> list[PinItem]: bookmarks = [] with open(PIN_PATH, "r") as f: loaded_pins = cast(PinsDict, json.load(f)) - for bookmark in loaded_pins["pins"]: - bookmark["path"]=normalise(_expand_vars(bookmark["path"])) - bookmarks.append(bookmark) + # for bookmark in loaded_pins["pins"]: + # bookmark["path"]=normalise(_expand_vars(bookmark["path"])) + # bookmarks.append(bookmark) + bookmarks = _sanitize(loaded_pins["pins"]) except (IOError, ValueError, json.decoder.JSONDecodeError): bookmarks = [] return bookmarks @register_bookmarks("gtk") -class GTKBookmarks(PinProvider): +class GTKBookmarks(): + bookmarks_path = "~/.config/gtk-3.0/bookmarks" + + @classmethod + def load_pins(cls) -> list[PinItem]: + bookmarks = [] + try: + with open(cls.bookmarks_path) as bookmarks_file: + for line in bookmarks_file.readlines(): + try: + path, name = line.strip().split(" ", 1) + bookmarks.append({"name": name, "path": str(Path.from_uri(path))}) + except ValueError: + pass + except OSError: + pass + return bookmarks + + +@register_bookmarks("kde") +class KDEBookmarks(): + bookmarks_path = "~/.local/share/kfile/bookmarks.xml" + @classmethod def load_pins(cls) -> list[PinItem]: bookmarks = [] - from pathlib import Path - with open(Path("~/.config/gtk-3.0/bookmarks").expanduser()) as bookmarks_file: - for line in bookmarks_file.readlines(): + try: + root = ET.parse(cls.bookmarks_path).getroot() + for elem in root.iter("bookmark"): try: - path, name = line.strip().split(" ", 1) - bookmarks.append({"name": name, "path": str(Path.from_uri(path))}) + title_elem = elem.find("title") + name = title_elem.text.strip() + path = str(Path.from_uri(elem.get("href", "").strip())) + bookmarks.append({"name": name, "path": path}) except ValueError: pass - return bookmarks + except OSError: + pass + return bookmarks def _expand_vars(path: str) -> str: for var in RovrVars.slots: @@ -138,18 +165,21 @@ def _expand_vars(path: str) -> str: return path -def load_pins(): +def _sanitize(pins: list[PinItem]) -> list[PinItem]: + out = [] + for pin in pins: + pin["path"] = normalise(_expand_vars(pin["path"])) + out.append(pin) + return out - # get the values from user config, not implemented yet - config_places = ["rovr"] - config_bookmarks = ["rovr"] # ["rovr", "gtk"] is possible +def load_pins() -> PinsDict: _places = [] _bookmarks = [] - for provider in config_places: + for provider in config["interface"]["pins_places"]: _places.extend(_places_providers[provider].load_pins()) - for provider in config_bookmarks: + for provider in config["interface"]["pins_bookmarks"]: _bookmarks.extend(_bookmarks_providers[provider].load_pins()) return { @@ -158,8 +188,6 @@ def load_pins(): } - - def add_pin(pin_name: str, pin_path: str | bytes) -> None: """ Add a pin to the pins file. From c08dc747405691a847b5fc45112285d941e35cc6 Mon Sep 17 00:00:00 2001 From: derVedro Date: Mon, 4 May 2026 08:01:08 +0200 Subject: [PATCH 3/3] schema update --- .../src/content/docs/dev/reference/schema.mdx | 119 ++++++++++++++---- 1 file changed, 98 insertions(+), 21 deletions(-) diff --git a/docs/src/content/docs/dev/reference/schema.mdx b/docs/src/content/docs/dev/reference/schema.mdx index 590adf3b..b0e711d5 100644 --- a/docs/src/content/docs/dev/reference/schema.mdx +++ b/docs/src/content/docs/dev/reference/schema.mdx @@ -38,6 +38,10 @@ description: config schema humanified - [1.18.2. Property `Rovr Config > interface > compact_mode > panels`](#interface_compact_mode_panels) - [1.19. Property `Rovr Config > interface > mime_rules`](#interface_mime_rules) - [1.19.1. Property `Rovr Config > interface > mime_rules > additionalProperties`](#interface_mime_rules_additionalProperties) + - [1.20. Property `Rovr Config > interface > pins_places`](#interface_pins_places) + - [1.20.1. Rovr Config > interface > pins_places > pins_places items](#interface_pins_places_items) + - [1.21. Property `Rovr Config > interface > pins_bookmarks`](#interface_pins_bookmarks) + - [1.21.1. Rovr Config > interface > pins_bookmarks > pins_bookmarks items](#interface_pins_bookmarks_items) - [2. Property `Rovr Config > settings`](#settings) - [2.1. Property `Rovr Config > settings > use_recycle_bin`](#settings_use_recycle_bin) - [2.2. Property `Rovr Config > settings > copy_includes_metadata`](#settings_copy_includes_metadata) @@ -352,27 +356,29 @@ description: config schema humanified **Description:** Settings related to the user interface and experience -| Property | Type | Title/Description | -| --------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [tooltips](#interface_tooltips) | boolean | Show tooltips when your mouse is over a tooltip supported button.
This is not hot reloaded. | -| [nerd_font](#interface_nerd_font) | boolean | Use nerd font for rendering icons instead of weird characters and stuff.
Not properly hot-reloaded. | -| [use_reactive_layout](#interface_use_reactive_layout) | boolean | Hide certain elements based on the width and height of the terminal. | -| [show_progress_eta](#interface_show_progress_eta) | boolean | When copying or deleting files, show an ETA for when the action will be completed. | -| [show_progress_percentage](#interface_show_progress_percentage) | boolean | When copying or deleting files, show a percentage of how much had been completed. | -| [truncate_progress_file_path](#interface_truncate_progress_file_path) | boolean | When the process container is using file paths, truncate the file path to only view the first and last names of the path. | -| [show_line_numbers](#interface_show_line_numbers) | boolean | Add line numbers to the left gutter if you are viewing a text file. | -| [scrolloff](#interface_scrolloff) | integer | The number of files to keep above and below the cursor when moving through the file list. | -| [show_hidden_files](#interface_show_hidden_files) | boolean | Show hidden files and folders (those starting with a dot on Unix, or explicitly hidden on Windows/MacOS). | -| [image_viewer](#interface_image_viewer) | object | Settings related to the image viewer used in the preview sidebar | -| [font_preview](#interface_font_preview) | object | Settings related to the font preview used in the preview sidebar | -| [allow_tab_nav](#interface_allow_tab_nav) | boolean | Allow navigating the main app screen with \`tab\` and \`shift+tab\` | -| [append_new_tabs](#interface_append_new_tabs) | boolean | Choose whether or not to append new tabs instead of inserting them.
\`true\` => Append to the end of the tab list.
\`false\` => Insert after the active tab. | -| [double_click_delay](#interface_double_click_delay) | number | The delay between two consecutive clicks to enter into a directory, or open a file. | -| [drive_watcher_frequency](#interface_drive_watcher_frequency) | number | How often (in seconds) to check for changes in mounted drives in the sidebar. | -| [clock](#interface_clock) | object | | -| [preview_text](#interface_preview_text) | object | | -| [compact_mode](#interface_compact_mode) | object | | -| [mime_rules](#interface_mime_rules) | object | Map MIME type patterns to preview types. Uses regex patterns. Valid preview types: text, image, pdf, archive, folder, resvg, font, remime.
-> Use 'remime' if you want a more accurate description from file(1) | +| Property | Type | Title/Description | +| --------------------------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [tooltips](#interface_tooltips) | boolean | Show tooltips when your mouse is over a tooltip supported button.
This is not hot reloaded. | +| [nerd_font](#interface_nerd_font) | boolean | Use nerd font for rendering icons instead of weird characters and stuff.
Not properly hot-reloaded. | +| [use_reactive_layout](#interface_use_reactive_layout) | boolean | Hide certain elements based on the width and height of the terminal. | +| [show_progress_eta](#interface_show_progress_eta) | boolean | When copying or deleting files, show an ETA for when the action will be completed. | +| [show_progress_percentage](#interface_show_progress_percentage) | boolean | When copying or deleting files, show a percentage of how much had been completed. | +| [truncate_progress_file_path](#interface_truncate_progress_file_path) | boolean | When the process container is using file paths, truncate the file path to only view the first and last names of the path. | +| [show_line_numbers](#interface_show_line_numbers) | boolean | Add line numbers to the left gutter if you are viewing a text file. | +| [scrolloff](#interface_scrolloff) | integer | The number of files to keep above and below the cursor when moving through the file list. | +| [show_hidden_files](#interface_show_hidden_files) | boolean | Show hidden files and folders (those starting with a dot on Unix, or explicitly hidden on Windows/MacOS). | +| [image_viewer](#interface_image_viewer) | object | Settings related to the image viewer used in the preview sidebar | +| [font_preview](#interface_font_preview) | object | Settings related to the font preview used in the preview sidebar | +| [allow_tab_nav](#interface_allow_tab_nav) | boolean | Allow navigating the main app screen with \`tab\` and \`shift+tab\` | +| [append_new_tabs](#interface_append_new_tabs) | boolean | Choose whether or not to append new tabs instead of inserting them.
\`true\` => Append to the end of the tab list.
\`false\` => Insert after the active tab. | +| [double_click_delay](#interface_double_click_delay) | number | The delay between two consecutive clicks to enter into a directory, or open a file. | +| [drive_watcher_frequency](#interface_drive_watcher_frequency) | number | How often (in seconds) to check for changes in mounted drives in the sidebar. | +| [clock](#interface_clock) | object | | +| [preview_text](#interface_preview_text) | object | | +| [compact_mode](#interface_compact_mode) | object | | +| [mime_rules](#interface_mime_rules) | object | Map MIME type patterns to preview types. Uses regex patterns. Valid preview types: text, image, pdf, archive, folder, resvg, font, remime.
-> Use 'remime' if you want a more accurate description from file(1) | +| [pins_places](#interface_pins_places) | array of enum (of string) | Provider for the pinned places in the side pane. | +| [pins_bookmarks](#interface_pins_bookmarks) | array of enum (of string) | Provider for the pinned bookmarks in the side pane. | ### 1.1. Property `Rovr Config > interface > tooltips` @@ -805,6 +811,77 @@ Must be one of: - "resvg" - "font" +### 1.20. Property `Rovr Config > interface > pins_places` + +| | | +| ------------ | --------------------------- | +| **Type** | `array of enum (of string)` | +| **Required** | No | +| **Default** | `["rovr"]` | + +**Description:** Provider for the pinned places in the side pane. + +| | Array restrictions | +| -------------------- | ------------------ | +| **Min items** | 1 | +| **Max items** | N/A | +| **Items unicity** | False | +| **Additional items** | False | +| **Tuple validation** | See below | + +| Each item of this array must be | Description | +| ------------------------------------------------- | ----------- | +| [pins_places items](#interface_pins_places_items) | | + +#### 1.20.1. Rovr Config > interface > pins_places > pins_places items + +| | | +| ------------ | ------------------ | +| **Type** | `enum (of string)` | +| **Required** | No | + +Must be one of: + +- "empty" +- "default" +- "rovr" + +### 1.21. Property `Rovr Config > interface > pins_bookmarks` + +| | | +| ------------ | --------------------------- | +| **Type** | `array of enum (of string)` | +| **Required** | No | +| **Default** | `["rovr"]` | + +**Description:** Provider for the pinned bookmarks in the side pane. + +| | Array restrictions | +| -------------------- | ------------------ | +| **Min items** | 1 | +| **Max items** | N/A | +| **Items unicity** | False | +| **Additional items** | False | +| **Tuple validation** | See below | + +| Each item of this array must be | Description | +| ------------------------------------------------------- | ----------- | +| [pins_bookmarks items](#interface_pins_bookmarks_items) | | + +#### 1.21.1. Rovr Config > interface > pins_bookmarks > pins_bookmarks items + +| | | +| ------------ | ------------------ | +| **Type** | `enum (of string)` | +| **Required** | No | + +Must be one of: + +- "empty" +- "rovr" +- "gtk" +- "kde" + ## 2. Property `Rovr Config > settings` | | |