clone of my dotfiles.ssp.sh
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

install duckdb as preview

+350
+7
yazi/package.toml
··· 1 + [[plugin.deps]] 2 + use = "wylie102/duckdb" 3 + rev = "906a512" 4 + hash = "b1da160716f80b213f0f859b5df57ed5" 5 + 6 + [flavor] 7 + deps = []
+22
yazi/plugins/duckdb.yazi/LICENSE
··· 1 + # MIT License 2 + 3 + Copyright (c) 2025 Thomas Wylie 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE. 22 +
+76
yazi/plugins/duckdb.yazi/README.md
··· 1 + # duckdb.yazi 2 + 3 + [duckdb](https://github.com/duckdb/duckdb) now in [yazi](https://github.com/sxyazi/yazi). 4 + 5 + <img width="1710" alt="Screenshot 2025-03-22 at 18 00 06" src="https://github.com/user-attachments/assets/db09fff9-2db1-4273-9ddf-34d0bf087967" /> 6 + 7 + ## Installation 8 + 9 + To install, use the command: 10 + 11 + ya pack -a wylie102/duckdb 12 + 13 + and add to your yazi.toml: 14 + 15 + [plugin] 16 + prepend_previewers = [ 17 + { mime = "text/csv", run = "duckdb" }, 18 + { name = "*.tsv", run = "duckdb" }, 19 + { name = "*.json", run = "duckdb" }, 20 + { name = "*.parquet", run = "duckdb" }, 21 + ] 22 + 23 + prepend_preloaders = [ 24 + { mime = "text/csv", run = "duckdb", multi = false }, 25 + { name = "*.tsv", run = "duckdb", multi = false }, 26 + { name = "*.json", run = "duckdb", multi = false }, 27 + { name = "*.parquet", run = "duckdb", multi = false }, 28 + ] 29 + 30 + ### Yazi 31 + 32 + [Installation installations](https://yazi-rs.github.io/docs/installation) 33 + 34 + ### duckdb 35 + 36 + [Installation instructions](https://duckdb.org/docs/installation/?version=stable&environment=cli&platform=macos&download_method=direct) 37 + 38 + ## Recommended plugins 39 + 40 + Use with a larger preview window or maximize the preview pane plugin: 41 + <https://github.com/yazi-rs/plugins/tree/main/toggle-pane.yazi> 42 + 43 + ## What does it do? 44 + 45 + This plugin previews your data files in yazi using DuckDB, with two available view modes: 46 + 47 + - Standard mode (default): Displays the file as a table. 48 + - Summarized mode: Uses DuckDB's summarize function, enhanced with custom formatting for readability. 49 + 50 + Supported file types: 51 + 52 + - .csv 53 + - .json 54 + - .parquet 55 + - .tsv 56 + 57 + ## New Features 58 + 59 + - Default preview mode is now "standard." 60 + - Preview mode can be toggled within yazi: 61 + - Press "K" at the top of the file to toggle between "standard" and "summarized." 62 + - Preview mode is remembered per file, even after switching files or restarting yazi. 63 + - Performance improvements through caching: 64 + - "Standard" and "summarized" views are cached upon first load, improving scrolling performance. 65 + 66 + ## Setup and usage changes 67 + 68 + Previously, preview mode was selected by setting an environment variable (`DUCKDB_PREVIEW_MODE`). 69 + 70 + The new version no longer uses environment variables. Toggle preview modes directly within yazi using the keybinding described above. 71 + 72 + Scrolling within both views (standard and summarized) is handled by pressing J (down) and K (up). Performance is significantly better due to caching. 73 + 74 + ## Preview 75 + 76 + <img width="1710" alt="Screenshot 2025-03-22 at 17 59 21" src="https://github.com/user-attachments/assets/ac006667-4281-4e0a-87a4-bfaeefc6f20b" />
+231
yazi/plugins/duckdb.yazi/main.lua
··· 1 + -- This function generates the SQL query based on the preview mode. 2 + local function generate_sql(job, mode) 3 + if mode == "standard" then 4 + return string.format("SELECT * FROM '%s' LIMIT 500", tostring(job.file.url)) 5 + else 6 + return string.format( 7 + [[SELECT 8 + column_name AS column, 9 + column_type AS type, 10 + count, 11 + approx_unique AS unique, 12 + null_percentage AS null, 13 + LEFT(min, 10) AS min, 14 + LEFT(max, 10) AS max, 15 + CASE 16 + WHEN column_type IN ('TIMESTAMP', 'DATE') THEN '-' 17 + WHEN avg IS NULL THEN 'NULL' 18 + WHEN TRY_CAST(avg AS DOUBLE) IS NULL THEN avg 19 + WHEN CAST(avg AS DOUBLE) < 100000 THEN CAST(ROUND(CAST(avg AS DOUBLE), 2) AS VARCHAR) 20 + WHEN CAST(avg AS DOUBLE) < 1000000 THEN CAST(ROUND(CAST(avg AS DOUBLE) / 1000, 1) AS VARCHAR) || 'k' 21 + WHEN CAST(avg AS DOUBLE) < 1000000000 THEN CAST(ROUND(CAST(avg AS DOUBLE) / 1000000, 2) AS VARCHAR) || 'm' 22 + WHEN CAST(avg AS DOUBLE) < 1000000000000 THEN CAST(ROUND(CAST(avg AS DOUBLE) / 1000000000, 2) AS VARCHAR) || 'b' 23 + ELSE '∞' 24 + END AS avg, 25 + CASE 26 + WHEN column_type IN ('TIMESTAMP', 'DATE') THEN '-' 27 + WHEN std IS NULL THEN 'NULL' 28 + WHEN TRY_CAST(std AS DOUBLE) IS NULL THEN std 29 + WHEN CAST(std AS DOUBLE) < 100000 THEN CAST(ROUND(CAST(std AS DOUBLE), 2) AS VARCHAR) 30 + WHEN CAST(std AS DOUBLE) < 1000000 THEN CAST(ROUND(CAST(std AS DOUBLE) / 1000, 1) AS VARCHAR) || 'k' 31 + WHEN CAST(std AS DOUBLE) < 1000000000 THEN CAST(ROUND(CAST(std AS DOUBLE) / 1000000, 2) AS VARCHAR) || 'm' 32 + WHEN CAST(std AS DOUBLE) < 1000000000000 THEN CAST(ROUND(CAST(std AS DOUBLE) / 1000000000, 2) AS VARCHAR) || 'b' 33 + ELSE '∞' 34 + END AS std, 35 + CASE 36 + WHEN column_type IN ('TIMESTAMP', 'DATE') THEN '-' 37 + WHEN q25 IS NULL THEN 'NULL' 38 + WHEN TRY_CAST(q25 AS DOUBLE) IS NULL THEN q25 39 + WHEN CAST(q25 AS DOUBLE) < 100000 THEN CAST(ROUND(CAST(q25 AS DOUBLE), 2) AS VARCHAR) 40 + WHEN CAST(q25 AS DOUBLE) < 1000000 THEN CAST(ROUND(CAST(q25 AS DOUBLE) / 1000, 1) AS VARCHAR) || 'k' 41 + WHEN CAST(q25 AS DOUBLE) < 1000000000 THEN CAST(ROUND(CAST(q25 AS DOUBLE) / 1000000, 2) AS VARCHAR) || 'm' 42 + WHEN CAST(q25 AS DOUBLE) < 1000000000000 THEN CAST(ROUND(CAST(q25 AS DOUBLE) / 1000000000, 2) AS VARCHAR) || 'b' 43 + ELSE '∞' 44 + END AS q25, 45 + CASE 46 + WHEN column_type IN ('TIMESTAMP', 'DATE') THEN '-' 47 + WHEN q50 IS NULL THEN 'NULL' 48 + WHEN TRY_CAST(q50 AS DOUBLE) IS NULL THEN q50 49 + WHEN CAST(q50 AS DOUBLE) < 100000 THEN CAST(ROUND(CAST(q50 AS DOUBLE), 2) AS VARCHAR) 50 + WHEN CAST(q50 AS DOUBLE) < 1000000 THEN CAST(ROUND(CAST(q50 AS DOUBLE) / 1000, 1) AS VARCHAR) || 'k' 51 + WHEN CAST(q50 AS DOUBLE) < 1000000000 THEN CAST(ROUND(CAST(q50 AS DOUBLE) / 1000000, 2) AS VARCHAR) || 'm' 52 + WHEN CAST(q50 AS DOUBLE) < 1000000000000 THEN CAST(ROUND(CAST(q50 AS DOUBLE) / 1000000000, 2) AS VARCHAR) || 'b' 53 + ELSE '∞' 54 + END AS q50, 55 + CASE 56 + WHEN column_type IN ('TIMESTAMP', 'DATE') THEN '-' 57 + WHEN q75 IS NULL THEN 'NULL' 58 + WHEN TRY_CAST(q75 AS DOUBLE) IS NULL THEN q75 59 + WHEN CAST(q75 AS DOUBLE) < 100000 THEN CAST(ROUND(CAST(q75 AS DOUBLE), 2) AS VARCHAR) 60 + WHEN CAST(q75 AS DOUBLE) < 1000000 THEN CAST(ROUND(CAST(q75 AS DOUBLE) / 1000, 1) AS VARCHAR) || 'k' 61 + WHEN CAST(q75 AS DOUBLE) < 1000000000 THEN CAST(ROUND(CAST(q75 AS DOUBLE) / 1000000, 2) AS VARCHAR) || 'm' 62 + WHEN CAST(q75 AS DOUBLE) < 1000000000000 THEN CAST(ROUND(CAST(q75 AS DOUBLE) / 1000000000, 2) AS VARCHAR) || 'b' 63 + ELSE '∞' 64 + END AS q75 65 + FROM (summarize FROM '%s')]], 66 + tostring(job.file.url) 67 + ) 68 + end 69 + end 70 + 71 + local function get_cache_path(job, type) 72 + local skip = job.skip 73 + job.skip = 0 74 + local base = ya.file_cache(job) 75 + job.skip = skip 76 + if not base then 77 + return nil 78 + end 79 + local suffix = ({ standard = "_standard.db", summarized = "_summarized.db", mode = "_mode.db" })[type or "standard"] 80 + return Url(tostring(base) .. suffix) 81 + end 82 + 83 + local function run_query(job, query, target) 84 + local args = {} 85 + if target ~= job.file.url then 86 + table.insert(args, tostring(target)) 87 + end 88 + table.insert(args, "-c") 89 + table.insert(args, query) 90 + local child = Command("duckdb"):args(args):stdout(Command.PIPED):stderr(Command.PIPED):spawn() 91 + if not child then 92 + return nil 93 + end 94 + local output, err = child:wait_with_output() 95 + if err then 96 + return nil 97 + end 98 + if not output.status.success then 99 + ya.err("DuckDB exited with error: " .. output.stderr) 100 + return nil 101 + end 102 + return output 103 + end 104 + 105 + local function create_cache(job, mode, path) 106 + local filename = job.file.url:name() or "unknown" 107 + if fs.cha(path) then 108 + return true 109 + end 110 + local sql = (mode == "mode") and "CREATE TABLE My_table AS SELECT 'standard' AS Preview_mode;" 111 + or string.format("CREATE TABLE My_table AS (%s);", generate_sql(job, mode)) 112 + local out = run_query(job, sql, path, mode == "mode" and "mode" or nil) 113 + if not out then 114 + ya.err("Preload - Failed to generate " .. mode .. " cache for file: " .. tostring(filename) .. ".") 115 + return false 116 + end 117 + return true 118 + end 119 + 120 + local function get_preview_mode(job) 121 + local mode = "standard" 122 + local mode_cache = get_cache_path(job, "mode") 123 + if not mode_cache then 124 + return mode 125 + end 126 + if not fs.cha(mode_cache) then 127 + create_cache(job, "mode", mode_cache) 128 + end 129 + local result = run_query(job, "SELECT Preview_mode FROM My_table LIMIT 1;", mode_cache, "mode") 130 + if result and result.stdout and result.stdout ~= "" then 131 + local value = result.stdout:lower() 132 + if value:match("summarized") then 133 + mode = "summarized" 134 + end 135 + end 136 + return mode 137 + end 138 + 139 + local function generate_query(target, job, limit, offset) 140 + local mode = get_preview_mode(job) 141 + if target == job.file.url then 142 + if mode == "standard" then 143 + return string.format("SELECT * FROM '%s' LIMIT %d OFFSET %d;", tostring(target), limit, offset) 144 + else 145 + local query = generate_sql(job, mode) 146 + return string.format("WITH query AS (%s) SELECT * FROM query LIMIT %d OFFSET %d;", query, limit, offset) 147 + end 148 + else 149 + return string.format("SELECT * FROM My_table LIMIT %d OFFSET %d;", limit, offset) 150 + end 151 + end 152 + 153 + local function set_preview_mode(job, mode) 154 + local mode_cache = get_cache_path(job, "mode") 155 + if not mode_cache then 156 + return false 157 + end 158 + run_query(job, "DELETE FROM My_table;", mode_cache, "mode") 159 + local sql = string.format("INSERT INTO My_table VALUES ('%s');", mode) 160 + local result = run_query(job, sql, mode_cache, "mode") 161 + if not result then 162 + ya.err("SetPreviewMode - Failed to update preview mode.") 163 + return false 164 + end 165 + return true 166 + end 167 + 168 + local M = {} 169 + 170 + function M:preload(job) 171 + local cache_standard = get_cache_path(job, "standard") 172 + local cache_summarized = get_cache_path(job, "summarized") 173 + if not cache_standard or not cache_summarized then 174 + return false 175 + end 176 + if fs.cha(cache_standard) and fs.cha(cache_summarized) then 177 + return true 178 + end 179 + local success = true 180 + success = create_cache(job, "standard", cache_standard) and success 181 + success = create_cache(job, "summarized", cache_summarized) and success 182 + return success 183 + end 184 + 185 + function M:peek(job) 186 + local raw_skip = job.skip or 0 187 + local skip = math.max(0, raw_skip - 50) 188 + if raw_skip > 0 and raw_skip < 50 then 189 + local current_mode = get_preview_mode(job) 190 + local new_mode = current_mode == "standard" and "summarized" or "standard" 191 + set_preview_mode(job, new_mode) 192 + skip = 0 193 + end 194 + job.skip = skip 195 + local mode = get_preview_mode(job) 196 + local cache = get_cache_path(job, mode) 197 + local file_url = job.file.url 198 + local target = cache 199 + local limit = job.area.h - 7 200 + local offset = skip 201 + if not cache or not fs.cha(cache) then 202 + target = file_url 203 + end 204 + local query = generate_query(target, job, limit, offset) 205 + local output = run_query(job, query, target) 206 + if not output or output.stdout == "" then 207 + if target ~= file_url then 208 + target = file_url 209 + query = generate_query(target, job, limit, offset) 210 + output = run_query(job, query, target) 211 + if not output or output.stdout == "" then 212 + return require("code"):peek(job) 213 + end 214 + else 215 + return require("code"):peek(job) 216 + end 217 + end 218 + ya.preview_widgets(job, { ui.Text.parse(output.stdout):area(job.area) }) 219 + end 220 + 221 + function M:seek(job) 222 + local OFFSET_BASE = 50 223 + local encoded_current_skip = cx.active.preview.skip or 0 224 + local current_skip = math.max(0, encoded_current_skip - OFFSET_BASE) 225 + local units = job.units or 0 226 + local new_skip = current_skip + units 227 + local encoded_skip = new_skip + OFFSET_BASE 228 + ya.manager_emit("peek", { encoded_skip, only_if = job.file.url }) 229 + end 230 + 231 + return M
+14
yazi/yazi.toml
··· 1 + [plugin] 2 + prepend_previewers = [ 3 + { mime = "text/csv", run = "duckdb" }, 4 + { name = ".tsv", run = "duckdb" }, 5 + { name = ".json", run = "duckdb" }, 6 + { name = "*.parquet", run = "duckdb" }, 7 + ] 8 + 9 + prepend_preloaders = [ 10 + { mime = "text/csv", run = "duckdb", multi = false }, 11 + { name = ".tsv", run = "duckdb", multi = false }, 12 + { name = ".json", run = "duckdb", multi = false }, 13 + { name = "*.parquet", run = "duckdb", multi = false }, 14 + ]