pi-readseek 0.8.5 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +7 -0
  2. package/dist/index.ts +138 -67
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -43,6 +43,12 @@ Add an optional `readseek` section to `~/.pi/agent/settings.json` (global) or
43
43
  "grep": {
44
44
  "maxLines": 2000,
45
45
  "maxBytes": 51200
46
+ },
47
+ "display": {
48
+ "read": "compact",
49
+ "grep": "compact",
50
+ "edit": "expanded",
51
+ "write": "expanded"
46
52
  }
47
53
  }
48
54
  }
@@ -58,6 +64,7 @@ Add an optional `readseek` section to `~/.pi/agent/settings.json` (global) or
58
64
  - `timeoutMs`: ReadSeek command timeout in milliseconds.
59
65
  - `grep.maxLines` and `grep.maxBytes`: output limits for `readSeek_grep`. Values
60
66
  above the defaults are clamped.
67
+ - `display`: default display state (`"compact"` or `"expanded"`) per tool for `read`, `grep`, `edit`, and `write`.
61
68
 
62
69
  ## Licensing
63
70
 
package/dist/index.ts CHANGED
@@ -1183,8 +1183,15 @@ var READSEEK_TOOL_REPLACEMENTS = {
1183
1183
  grep: "readSeek_grep",
1184
1184
  write: "readSeek_write"
1185
1185
  };
1186
- var READSEEK_KEYS = ["replacedTools", "imageMode", "syntaxValidation", "timeoutMs", "grep"];
1186
+ var READSEEK_KEYS = ["replacedTools", "imageMode", "syntaxValidation", "timeoutMs", "grep", "display"];
1187
1187
  var READSEEK_GREP_KEYS = ["maxLines", "maxBytes"];
1188
+ var READSEEK_DISPLAY_KEYS = ["read", "grep", "edit", "write"];
1189
+ var DEFAULT_DISPLAY_MODES = {
1190
+ read: "compact",
1191
+ grep: "compact",
1192
+ edit: "expanded",
1193
+ write: "expanded"
1194
+ };
1188
1195
  function globalSettingsPath() {
1189
1196
  return join(homedir2(), ".pi/agent/settings.json");
1190
1197
  }
@@ -1250,6 +1257,22 @@ function readReplacedTools(raw, source, warnings) {
1250
1257
  });
1251
1258
  return tools;
1252
1259
  }
1260
+ function readDisplaySettings(raw, source, warnings) {
1261
+ if (!("display" in raw))
1262
+ return;
1263
+ if (!isRecord(raw.display)) {
1264
+ warnings.push(invalid(source, "readseek.display"));
1265
+ return;
1266
+ }
1267
+ warnUnknownKeys(raw.display, READSEEK_DISPLAY_KEYS, "readseek.display", source, warnings);
1268
+ const display = {};
1269
+ for (const tool of READSEEK_DISPLAY_KEYS) {
1270
+ const mode = readEnum(raw.display, tool, ["compact", "expanded"], `readseek.display.${tool}`, source, warnings);
1271
+ if (mode !== undefined)
1272
+ display[tool] = mode;
1273
+ }
1274
+ return Object.keys(display).length > 0 ? display : undefined;
1275
+ }
1253
1276
  function validateSettings(raw, source) {
1254
1277
  const settings = {};
1255
1278
  const warnings = [];
@@ -1302,6 +1325,9 @@ function validateSettings(raw, source) {
1302
1325
  warnings.push(invalid(source, "readseek.grep"));
1303
1326
  }
1304
1327
  }
1328
+ const display = readDisplaySettings(section, source, warnings);
1329
+ if (display !== undefined)
1330
+ settings.display = display;
1305
1331
  return { settings, warnings };
1306
1332
  }
1307
1333
  var settingsFileCache = new Map;
@@ -1337,6 +1363,9 @@ function mergeSettings(base, override) {
1337
1363
  const grep = { ...base.grep ?? {}, ...override.grep ?? {} };
1338
1364
  if (Object.keys(grep).length > 0)
1339
1365
  settings.grep = grep;
1366
+ const display = { ...base.display ?? {}, ...override.display ?? {} };
1367
+ if (Object.keys(display).length > 0)
1368
+ settings.display = display;
1340
1369
  return settings;
1341
1370
  }
1342
1371
  function resolveReadSeekJsonSettings() {
@@ -1356,6 +1385,9 @@ function resolveReadSeekSyntaxValidation() {
1356
1385
  function resolveReadSeekTimeoutMs() {
1357
1386
  return resolveReadSeekJsonSettings().settings.timeoutMs;
1358
1387
  }
1388
+ function resolveReadSeekToolDisplayMode(tool) {
1389
+ return resolveReadSeekJsonSettings().settings.display?.[tool] ?? DEFAULT_DISPLAY_MODES[tool];
1390
+ }
1359
1391
 
1360
1392
  // src/readseek-client.ts
1361
1393
  function normalizeLanguage(language) {
@@ -2946,15 +2978,15 @@ function summaryLine(summary, options = {}) {
2946
2978
  const prefix = options.theme ? options.theme.fg(options.style ?? "dim", SUMMARY_PREFIX) : SUMMARY_PREFIX;
2947
2979
  return appendExpandHint(`${prefix} ${summary}`, !!options.hidden);
2948
2980
  }
2949
- function isRendererExpanded(options, context) {
2950
- return context?.expanded ?? options?.expanded ?? false;
2981
+ function isRendererExpanded(options, context, defaultExpanded = false) {
2982
+ return context?.expanded ?? options?.expanded ?? defaultExpanded;
2951
2983
  }
2952
- function resolveRenderResultContext(options, rest) {
2984
+ function resolveRenderResultContext(options, rest, defaultExpanded = false) {
2953
2985
  const context = rest[0] ?? options ?? {};
2954
2986
  return {
2955
2987
  isPartial: context.isPartial ?? options?.isPartial ?? false,
2956
2988
  isError: context.isError ?? false,
2957
- expanded: isRendererExpanded(options, context),
2989
+ expanded: isRendererExpanded(options, context, defaultExpanded),
2958
2990
  width: context.width ?? options?.width,
2959
2991
  cwd: context.cwd ?? process.cwd(),
2960
2992
  context
@@ -2990,50 +3022,7 @@ function wrapWithHangingIndent(prefix, content, width, options = {}) {
2990
3022
  const indent = " ".repeat(prefixWidth);
2991
3023
  return wrapped.map((line, index) => tint(clampLineToWidth(index === 0 ? prefix + line : indent + line, normalized)));
2992
3024
  }
2993
- var HASHLINE_CONTENT_RE = /^(\d+:[0-9a-fA-F]+\|)(.*)$/;
2994
- function wrapReadHashlinesForWidth(text, width) {
2995
- if (width === undefined || width === null)
2996
- return text;
2997
- const normalized = normalizeWidth(width);
2998
- const output = [];
2999
- for (const line of text.split(`
3000
- `)) {
3001
- const match = line.match(HASHLINE_CONTENT_RE);
3002
- if (!match) {
3003
- output.push(line);
3004
- continue;
3005
- }
3006
- if (visibleWidth(line) <= normalized) {
3007
- output.push(line);
3008
- continue;
3009
- }
3010
- const prefix = match[1];
3011
- const content = match[2] ?? "";
3012
- const prefixWidth = visibleWidth(prefix);
3013
- const contentWidth = Math.max(1, normalized - prefixWidth);
3014
- const wrappedContent = wrapTextWithAnsi(content, contentWidth).map((wrapped) => clampLineToWidth(wrapped, contentWidth));
3015
- if (wrappedContent.length === 0) {
3016
- output.push(clampLineToWidth(prefix, normalized));
3017
- continue;
3018
- }
3019
- output.push(clampLineToWidth(prefix + wrappedContent[0], normalized));
3020
- const indent = " ".repeat(prefixWidth);
3021
- for (const continuation of wrappedContent.slice(1)) {
3022
- output.push(clampLineToWidth(indent + continuation, normalized));
3023
- }
3024
- }
3025
- return output.join(`
3026
- `);
3027
- }
3028
3025
  var wrappedHashlinesCache = new WeakMap;
3029
- function wrapReadHashlinesForWidthCached(cacheKey, text, width) {
3030
- const cached = wrappedHashlinesCache.get(cacheKey);
3031
- if (cached && cached.text === text && cached.width === width)
3032
- return cached.wrapped;
3033
- const wrapped = wrapReadHashlinesForWidth(text, width);
3034
- wrappedHashlinesCache.set(cacheKey, { text, width, wrapped });
3035
- return wrapped;
3036
- }
3037
3026
  function renderPendingResult(pendingLabel, width, theme) {
3038
3027
  return new Text(clampLinesToWidth([summaryLine(pendingLabel, { theme, style: "muted" })], width).join(`
3039
3028
  `), 0, 0);
@@ -3093,6 +3082,90 @@ ${linkedPathLine(theme, file.path, display, cwd, ` (${file.lines.length})`, widt
3093
3082
  `), 0, 0);
3094
3083
  }
3095
3084
 
3085
+ // src/tui-source-render.ts
3086
+ import { getLanguageFromPath, highlightCode } from "@earendil-works/pi-coding-agent";
3087
+ import { wrapTextWithAnsi as wrapTextWithAnsi2 } from "@earendil-works/pi-tui";
3088
+ var READ_ANCHOR_RE = /^(\d+:[0-9a-zA-Z]{1,16})\|(.*)$/;
3089
+ var GREP_ANCHOR_RE = /^(.*?):(>>| )(\d+):([0-9a-zA-Z]{1,16})\|(.*)$/;
3090
+ var SOURCE_THEME_STYLES = [
3091
+ "mdCodeBlock",
3092
+ "muted",
3093
+ "syntaxComment",
3094
+ "syntaxKeyword",
3095
+ "syntaxFunction",
3096
+ "syntaxVariable",
3097
+ "syntaxString",
3098
+ "syntaxNumber",
3099
+ "syntaxType",
3100
+ "syntaxOperator",
3101
+ "syntaxPunctuation",
3102
+ "toolDiffAdded",
3103
+ "toolDiffRemoved"
3104
+ ];
3105
+ function wrapDisplayLines(lines, width) {
3106
+ if (width === undefined || width === null)
3107
+ return lines;
3108
+ const normalized = normalizeWidth(width);
3109
+ return lines.flatMap((line) => {
3110
+ const wrapped = wrapTextWithAnsi2(line, normalized);
3111
+ return wrapped.length > 0 ? wrapped : [""];
3112
+ });
3113
+ }
3114
+ function renderSourceBlock(lines, path2) {
3115
+ return highlightCode(lines.join(`
3116
+ `), getLanguageFromPath(path2));
3117
+ }
3118
+ function renderReadSourceForDisplay(text, path2, anchors, width) {
3119
+ const output = [];
3120
+ let sourceLines = [];
3121
+ const flushSourceLines = () => {
3122
+ if (sourceLines.length === 0)
3123
+ return;
3124
+ output.push(...wrapDisplayLines(renderSourceBlock(sourceLines, path2), width));
3125
+ sourceLines = [];
3126
+ };
3127
+ for (const line of text.split(`
3128
+ `)) {
3129
+ const match = line.match(READ_ANCHOR_RE);
3130
+ if (match && anchors.has(match[1])) {
3131
+ sourceLines.push(match[2] ?? "");
3132
+ continue;
3133
+ }
3134
+ flushSourceLines();
3135
+ output.push(...wrapDisplayLines([line], width));
3136
+ }
3137
+ flushSourceLines();
3138
+ return output.join(`
3139
+ `);
3140
+ }
3141
+ var readSourceCache = new WeakMap;
3142
+ function renderReadSourceForDisplayCached(cacheKey, text, path2, anchors, width, theme) {
3143
+ const themeKey = SOURCE_THEME_STYLES.map((style) => theme.fg(style, "")).join("");
3144
+ const anchorKey = [...anchors].join(`
3145
+ `);
3146
+ const cached = readSourceCache.get(cacheKey);
3147
+ if (cached && cached.text === text && cached.path === path2 && cached.width === width && cached.theme === themeKey && cached.anchors === anchorKey) {
3148
+ return cached.rendered;
3149
+ }
3150
+ const rendered = renderReadSourceForDisplay(text, path2, anchors, width);
3151
+ readSourceCache.set(cacheKey, { text, path: path2, width, theme: themeKey, anchors: anchorKey, rendered });
3152
+ return rendered;
3153
+ }
3154
+ function renderGrepSourceForDisplay(text, anchors, renderPath = (path2) => path2) {
3155
+ return text.split(`
3156
+ `).map((line) => {
3157
+ const match = line.match(GREP_ANCHOR_RE);
3158
+ if (!match)
3159
+ return line;
3160
+ const anchor = `${match[3]}:${match[4]}`;
3161
+ if (!anchors.has(anchor))
3162
+ return line;
3163
+ const separator = match[2] === ">>" ? ":" : "-";
3164
+ return `${renderPath(match[1])}${separator}${match[3]}${separator}${match[5] ?? ""}`;
3165
+ }).join(`
3166
+ `);
3167
+ }
3168
+
3096
3169
  // src/register-tool.ts
3097
3170
  import { Type } from "@sinclair/typebox";
3098
3171
  function optionalIntOrString(description) {
@@ -3644,7 +3717,7 @@ function registerReadTool(pi, options = {}) {
3644
3717
  return new Text2(clampLineToWidth(text, context.width), 0, 0);
3645
3718
  },
3646
3719
  renderResult(result, options2, theme, ...rest) {
3647
- const { isPartial, isError, expanded, width } = resolveRenderResultContext(options2, rest);
3720
+ const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("read") === "expanded");
3648
3721
  if (isPartial)
3649
3722
  return renderPendingResult("pending read", width, theme);
3650
3723
  const content = result.content?.[0];
@@ -3677,9 +3750,16 @@ function registerReadTool(pi, options = {}) {
3677
3750
  summaryParts.push(badge);
3678
3751
  const summary = summaryParts.join(" • ");
3679
3752
  let text = summaryLine(summary, { hidden: !!textContent && !expanded, theme, style: "success" });
3680
- if (expanded && textContent)
3753
+ if (expanded && textContent) {
3754
+ const anchors = new Set(readSeekValue.lines?.map((line) => line.anchor) ?? []);
3755
+ for (const item of readSeekValue.bundle?.localSupport ?? []) {
3756
+ for (const anchor of item.lineAnchors)
3757
+ anchors.add(anchor);
3758
+ }
3759
+ const sourcePath = readSeekValue.path ?? context.args?.path ?? "";
3681
3760
  text += `
3682
- ` + wrapReadHashlinesForWidthCached(content, textContent, width);
3761
+ ` + renderReadSourceForDisplayCached(content, textContent, sourcePath, anchors, width, theme);
3762
+ }
3683
3763
  return new Text2(clampLinesToWidth(text.split(`
3684
3764
  `), width).join(`
3685
3765
  `), 0, 0);
@@ -5170,7 +5250,7 @@ function registerEditTool(pi, options = {}) {
5170
5250
  `));
5171
5251
  },
5172
5252
  renderResult(result, options2, theme, ...rest) {
5173
- const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options2, rest);
5253
+ const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("edit") === "expanded");
5174
5254
  if (isPartial) {
5175
5255
  return renderPendingResult("pending edit", width, theme);
5176
5256
  }
@@ -5995,7 +6075,7 @@ function registerGrepTool(pi, options = {}) {
5995
6075
  return new Text5(clampLineToWidth(text, context.width), 0, 0);
5996
6076
  },
5997
6077
  renderResult(result, options2, theme, ...rest) {
5998
- const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options2, rest);
6078
+ const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("grep") === "expanded");
5999
6079
  if (isPartial)
6000
6080
  return renderPendingResult("pending grep", width, theme);
6001
6081
  const content = result.content?.[0];
@@ -6027,19 +6107,10 @@ function registerGrepTool(pi, options = {}) {
6027
6107
  });
6028
6108
  for (const badge of info.badges)
6029
6109
  text += theme.fg(badge.startsWith("⚠") ? "warning" : "dim", ` ${badge}`);
6030
- if (expanded && readSeekValue?.records) {
6031
- const fileCounts = new Map;
6032
- for (const r of readSeekValue.records)
6033
- if (r.path && r.kind === "match")
6034
- fileCounts.set(r.path, (fileCounts.get(r.path) ?? 0) + 1);
6035
- for (const [filePath, count] of [...fileCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 20)) {
6036
- const display = path2.relative(cwd, filePath) || filePath;
6037
- text += `
6038
- ${linkedPathLine(theme, filePath, display, cwd, ` (${count})`, width)}`;
6039
- }
6040
- if (fileCounts.size > 20)
6041
- text += `
6042
- ` + theme.fg("muted", ` … and ${fileCounts.size - 20} more files`);
6110
+ if (expanded && textContent && readSeekValue?.records) {
6111
+ const anchors = new Set(readSeekValue.records.map((record) => record.anchor));
6112
+ text += `
6113
+ ` + renderGrepSourceForDisplay(textContent, anchors, (displayPath) => linkToolPath(theme.fg("dim", displayPath), displayPath, cwd));
6043
6114
  }
6044
6115
  return new Text5(clampLinesToWidth(text.split(`
6045
6116
  `), width).join(`
@@ -6884,7 +6955,7 @@ function registerWriteTool(pi, options = {}) {
6884
6955
  `));
6885
6956
  },
6886
6957
  renderResult(result, options2, theme, ...rest) {
6887
- const { isPartial, expanded, width, context } = resolveRenderResultContext(options2, rest);
6958
+ const { isPartial, expanded, width, context } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("write") === "expanded");
6888
6959
  if (isPartial)
6889
6960
  return renderPendingResult("pending write", width, theme);
6890
6961
  const details = result.details ?? {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-readseek",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Pi extension for readseek-backed hash-anchored read/edit/grep, structural code maps, structural search, and file exploration",
5
5
  "type": "module",
6
6
  "exports": {
@@ -40,7 +40,7 @@
40
40
  "node": ">=20.0.0"
41
41
  },
42
42
  "dependencies": {
43
- "@jarkkojs/readseek": "^0.8.5",
43
+ "@jarkkojs/readseek": "^0.8.7",
44
44
  "diff": "^8.0.3",
45
45
  "xxhash-wasm": "^1.1.0"
46
46
  },