diff --git a/zed-provider/README.md b/zed-provider/README.md new file mode 100644 index 0000000..eee0ef5 --- /dev/null +++ b/zed-provider/README.md @@ -0,0 +1,38 @@ +# Zed Provider + +![Zed Provider thumbnail](thumbnail.webp) + +Zed Provider integrates recent Zed workspaces with the Noctalia launcher so you +can reopen a project quickly without leaving the shell. + +## Plugin + +| Field | Value | +| --- | --- | +| ID | `cleboost/zed-provider` | +| Entry | Launcher provider: `provider` | +| Launcher Prefix | `/zed` | + +## Requirements + +Install [Zed](https://zed.dev) and ensure `zed` is available on `PATH` as +`zeditor`. The `sqlite3` command must also be available to read Zed's workspace +database. + +## Usage + +Open the Noctalia launcher and type `/zed` to list recent local Zed workspaces. +Continue typing to filter projects by name, then select one to open it with +`zeditor`. + +## Settings + +- `db_path` — path to Zed's `db.sqlite` workspace database. +- `max_results` — maximum number of projects shown in the launcher. + +## Notes + +Projects are read from Zed's workspace database, typically at +`~/.local/share/zed/db/0-stable/db.sqlite`. Remote workspaces are excluded. +The list is cached for the launcher session and refreshed when you clear the +query. diff --git a/zed-provider/plugin.toml b/zed-provider/plugin.toml new file mode 100644 index 0000000..a9743f0 --- /dev/null +++ b/zed-provider/plugin.toml @@ -0,0 +1,34 @@ +id = "cleboost/zed-provider" +name = "Zed Provider" +version = "1.0.0" +plugin_api = 3 +author = "cleboost" +license = "MIT" +icon = "folder-open" +description = "Open a recent Zed project from the launcher. Type /zed to list your projects." +tags = ["launcher", "development", "productivity"] +dependencies = ["sqlite3", "zed", "nohup"] + +[[setting]] +key = "db_path" +type = "file" +label_key = "settings.db_path.label" +description_key = "settings.db_path.description" +default = "~/.local/share/zed/db/0-stable/db.sqlite" + +[[setting]] +key = "max_results" +type = "int" +label_key = "settings.max_results.label" +description_key = "settings.max_results.description" +default = 20 +min = 1 +max = 100 + +[[launcher_provider]] +id = "provider" +entry = "zed_provider.luau" +prefix = "zed" +glyph = "bolt" +include_in_global_search = false +debounce_ms = 0 diff --git a/zed-provider/thumbnail.webp b/zed-provider/thumbnail.webp new file mode 100644 index 0000000..5ccde31 Binary files /dev/null and b/zed-provider/thumbnail.webp differ diff --git a/zed-provider/translations/en.json b/zed-provider/translations/en.json new file mode 100644 index 0000000..c2814a8 --- /dev/null +++ b/zed-provider/translations/en.json @@ -0,0 +1,11 @@ +{ + "loading": "Loading…", + "loading-subtitle": "Reading Zed projects", + "no-projects-found": "No projects found", + "filter-empty": "Filter: \"{filter}\"", + "database-empty": "Zed database is empty or not found", + "settings.db_path.label": "Zed database path", + "settings.db_path.description": "Path to Zed's db.sqlite file.", + "settings.max_results.label": "Maximum results", + "settings.max_results.description": "Maximum number of projects to display." +} diff --git a/zed-provider/translations/fr.json b/zed-provider/translations/fr.json new file mode 100644 index 0000000..795ff90 --- /dev/null +++ b/zed-provider/translations/fr.json @@ -0,0 +1,11 @@ +{ + "loading": "Chargement…", + "loading-subtitle": "Lecture des projets Zed", + "no-projects-found": "Aucun projet trouvé", + "filter-empty": "Filtre : « {filter} »", + "database-empty": "La base Zed est vide ou introuvable", + "settings.db_path.label": "Chemin de la base Zed", + "settings.db_path.description": "Chemin vers le fichier db.sqlite de Zed.", + "settings.max_results.label": "Nombre maximum de résultats", + "settings.max_results.description": "Nombre maximum de projets à afficher." +} diff --git a/zed-provider/zed_provider.luau b/zed-provider/zed_provider.luau new file mode 100644 index 0000000..1858690 --- /dev/null +++ b/zed-provider/zed_provider.luau @@ -0,0 +1,124 @@ +--!nonstrict + +local cachedProjects = nil + +local function getDbPath() + local configured = noctalia.getConfig("db_path") + if type(configured) == "string" and configured ~= "" then + return noctalia.expandPath(configured) + end + return noctalia.expandPath("~/.local/share/zed/db/0-stable/db.sqlite") +end + +local function getMaxResults() + local n = noctalia.getConfig("max_results") + return (type(n) == "number" and n > 0) and math.floor(n) or 20 +end + +local function trim(s) + return s:match("^%s*(.-)%s*$") +end + +local function projectName(path) + return path:match("([^/]+)$") or path +end + +local function shellQuote(s) + return "'" .. s:gsub("'", "'\"'\"'") .. "'" +end + +local function loadProjects(onDone) + local db = getDbPath() + local limit = getMaxResults() + local sql = string.format( + "SELECT DISTINCT paths FROM workspaces WHERE paths IS NOT NULL AND paths != '' AND remote_connection_id IS NULL ORDER BY timestamp DESC LIMIT %d;", + limit + ) + local cmd = string.format("sqlite3 -separator '\\n' %s %s 2>/dev/null", shellQuote(db), shellQuote(sql)) + + noctalia.runAsync(cmd, function(result) + local projects = {} + if result.exitCode == 0 and type(result.stdout) == "string" then + for line in result.stdout:gmatch("[^\n]+") do + local path = trim(line) + if path ~= "" then + table.insert(projects, path) + end + end + end + cachedProjects = projects + onDone(projects) + end) +end + +local function makeRows(projects, filter) + local home = noctalia.getenv("HOME") or "" + local rows = {} + for _, path in projects do + local name = projectName(path) + local score = filter == "" and 0 or noctalia.fuzzyScore(filter, name) + if score == nil then + score = noctalia.fuzzyScore(filter, path) + end + if filter == "" or score ~= nil then + local display = (home ~= "" and path:sub(1, #home) == home) and "~" .. path:sub(#home + 1) or path + table.insert(rows, { + id = path, + title = name, + subtitle = display, + icon = "zed", + score = filter == "" and nil or score, + }) + end + end + return rows +end + +local function showResults(query, projects, filter) + local rows = makeRows(projects, filter) + if #rows == 0 then + launcher.setResults(query, { + { + id = "", + title = noctalia.tr("no-projects-found"), + subtitle = filter ~= "" and noctalia.tr("filter-empty", { filter = filter }) or noctalia.tr("database-empty"), + glyph = "folder-x", + }, + }) + else + launcher.setResults(query, rows) + end +end + +function onQuery(query) + local filter = trim(query) + + if filter == "" then + cachedProjects = nil + end + + if cachedProjects then + showResults(query, cachedProjects, filter) + return + end + + launcher.setResults(query, { + { + id = "", + title = noctalia.tr("loading"), + subtitle = noctalia.tr("loading-subtitle"), + glyph = "loader", + }, + }) + + loadProjects(function(projects) + showResults(query, projects, filter) + end) +end + +function onActivate(id) + if id == "" then + return + end + noctalia.runAsync(string.format("nohup zeditor %s &>/dev/null &", shellQuote(id))) +end