tmux-fuzzy-motion 0.0.8 → 0.0.9

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 (2) hide show
  1. package/dist/cli.js +76 -17
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4727,7 +4727,7 @@ const capturePane = async (tmux, paneId) => {
4727
4727
  };
4728
4728
  //#endregion
4729
4729
  //#region src/core/width.ts
4730
- const ANSI_PATTERN = /\u001B\[[0-9;]*m/gu;
4730
+ const ANSI_PATTERN$1 = /\u001B\[[0-9;]*m/gu;
4731
4731
  const RESET$2 = "\x1B[0m";
4732
4732
  const displayWidth = (value) => stringWidth(value);
4733
4733
  const codeUnitIndexToColumn = (value, index) => stringWidth(value.slice(0, index));
@@ -4765,11 +4765,52 @@ const codeUnitRangeToColumns = (value, start, end) => {
4765
4765
  }
4766
4766
  return columns;
4767
4767
  };
4768
+ const createCompactStyledDisplayCells = (value) => {
4769
+ const cells = [];
4770
+ let activeStyle = "";
4771
+ let pendingStyle = "";
4772
+ let styleOpen = false;
4773
+ let lastVisibleCellIndex = -1;
4774
+ let lastIndex = 0;
4775
+ const appendChunk = (chunk) => {
4776
+ for (const char of chunk) {
4777
+ const width = Math.max(1, stringWidth(char));
4778
+ const prefix = pendingStyle.length > 0 ? pendingStyle : !styleOpen && activeStyle.length > 0 ? activeStyle : "";
4779
+ cells.push(`${prefix}${char}`);
4780
+ if (prefix.length > 0) {
4781
+ pendingStyle = "";
4782
+ styleOpen = true;
4783
+ }
4784
+ lastVisibleCellIndex = cells.length - 1;
4785
+ for (let offset = 1; offset < width; offset += 1) cells.push("");
4786
+ }
4787
+ };
4788
+ const closeStyle = () => {
4789
+ if (styleOpen && lastVisibleCellIndex >= 0) cells[lastVisibleCellIndex] = `${cells[lastVisibleCellIndex]}${RESET$2}`;
4790
+ activeStyle = "";
4791
+ pendingStyle = "";
4792
+ styleOpen = false;
4793
+ };
4794
+ for (const match of value.matchAll(ANSI_PATTERN$1)) {
4795
+ const start = match.index ?? 0;
4796
+ appendChunk(value.slice(lastIndex, start));
4797
+ const sequence = match[0];
4798
+ if (sequence === RESET$2) closeStyle();
4799
+ else {
4800
+ activeStyle = `${activeStyle}${sequence}`;
4801
+ pendingStyle = `${pendingStyle}${sequence}`;
4802
+ }
4803
+ lastIndex = start + sequence.length;
4804
+ }
4805
+ appendChunk(value.slice(lastIndex));
4806
+ if (styleOpen) closeStyle();
4807
+ return cells;
4808
+ };
4768
4809
  const createStyledDisplayCells = (value) => {
4769
4810
  const cells = [];
4770
4811
  let activeStyle = "";
4771
4812
  let lastIndex = 0;
4772
- for (const match of value.matchAll(ANSI_PATTERN)) {
4813
+ for (const match of value.matchAll(ANSI_PATTERN$1)) {
4773
4814
  const start = match.index ?? 0;
4774
4815
  const chunk = value.slice(lastIndex, start);
4775
4816
  for (const char of chunk) {
@@ -4816,9 +4857,19 @@ const PATTERNS = [
4816
4857
  }
4817
4858
  ];
4818
4859
  const isContainedByHigherPriority = (current, others) => others.some((candidate) => candidate.line === current.line && candidate.priority < current.priority && candidate.startIndex <= current.startIndex && candidate.endIndex >= current.endIndex);
4860
+ const dedupeLineCandidates = (candidates) => {
4861
+ const byStartIndex = /* @__PURE__ */ new Map();
4862
+ for (const candidate of candidates) {
4863
+ const current = byStartIndex.get(candidate.startIndex);
4864
+ if (!current || candidate.priority < current.priority) byStartIndex.set(candidate.startIndex, candidate);
4865
+ }
4866
+ const deduped = [...byStartIndex.values()];
4867
+ return deduped.filter((candidate) => !isContainedByHigherPriority(candidate, deduped));
4868
+ };
4819
4869
  const extractCandidates = (lines) => {
4820
4870
  const collected = [];
4821
4871
  lines.forEach((lineText, lineIndex) => {
4872
+ const lineCandidates = [];
4822
4873
  PATTERNS.forEach(({ kind, pattern }, priority) => {
4823
4874
  const regex = new RegExp(pattern.source, pattern.flags);
4824
4875
  for (const match of lineText.matchAll(regex)) {
@@ -4829,7 +4880,7 @@ const extractCandidates = (lines) => {
4829
4880
  if (width === 1 && kind !== "word") continue;
4830
4881
  const col = codeUnitIndexToColumn(lineText, startIndex);
4831
4882
  const charCol = codeUnitIndexToCharacterIndex(lineText, startIndex);
4832
- collected.push({
4883
+ lineCandidates.push({
4833
4884
  kind,
4834
4885
  text,
4835
4886
  line: lineIndex + 1,
@@ -4842,10 +4893,9 @@ const extractCandidates = (lines) => {
4842
4893
  });
4843
4894
  }
4844
4895
  });
4896
+ collected.push(...dedupeLineCandidates(lineCandidates));
4845
4897
  });
4846
- return collected.filter((candidate, _, source) => {
4847
- return source.find((other) => other.line === candidate.line && other.startIndex === candidate.startIndex && other.priority <= candidate.priority) === candidate;
4848
- }).filter((candidate, _, source) => !isContainedByHigherPriority(candidate, source)).sort((left, right) => left.line - right.line || left.col - right.col || left.priority - right.priority).map((candidate) => ({
4898
+ return collected.sort((left, right) => left.line - right.line || left.col - right.col || left.priority - right.priority).map((candidate) => ({
4849
4899
  kind: candidate.kind,
4850
4900
  text: candidate.text,
4851
4901
  line: candidate.line,
@@ -5961,6 +6011,7 @@ const withRawMode = async (input, output, callback) => {
5961
6011
  //#region src/commands/input.ts
5962
6012
  const QUERY_STYLE = "\x1B[48;5;236;38;5;252m";
5963
6013
  const RESET = "\x1B[0m";
6014
+ const ANSI_PATTERN = /\u001B\[[0-9;]*m/u;
5964
6015
  const HINT_CHARS = /* @__PURE__ */ new Set("ASDFGHJKLQWERTYUIOPZXCVBNM");
5965
6016
  const WORD_CHAR_PATTERN = /[a-z0-9_-]/u;
5966
6017
  const parsePopupArgs = (args) => {
@@ -6036,9 +6087,14 @@ const fitBodyToHeight = (lines, height) => {
6036
6087
  return next;
6037
6088
  };
6038
6089
  const renderQueryOnBottomLine = (line, width, query) => {
6090
+ if (query.length === 0) {
6091
+ const cells = createCompactStyledDisplayCells(line);
6092
+ while (cells.length < width) cells.push(" ");
6093
+ const rendered = cells.slice(0, width).join("");
6094
+ return ANSI_PATTERN.test(rendered) && !rendered.endsWith(RESET) ? `${rendered}${RESET}` : rendered;
6095
+ }
6039
6096
  const cells = createStyledDisplayCells(line);
6040
6097
  while (cells.length < width) cells.push(" ");
6041
- if (query.length === 0) return cells.slice(0, width).join("");
6042
6098
  const queryWidth = Math.min(width, stringWidth(query));
6043
6099
  const start = Math.max(0, width - queryWidth);
6044
6100
  const content = Array.from(query);
@@ -6055,7 +6111,7 @@ const renderQueryOnBottomLine = (line, width, query) => {
6055
6111
  return cells.slice(0, width).join("");
6056
6112
  };
6057
6113
  const createFrame = (state, query, matches, renderOverlay) => {
6058
- const body = fitBodyToHeight(query.length > 0 && matches.length > 0 ? renderOverlay(matches) : state.displayLines, state.height);
6114
+ const body = fitBodyToHeight(query.length > 0 && matches.length > 0 && renderOverlay ? renderOverlay(matches) : state.displayLines, state.height);
6059
6115
  const lastLineIndex = Math.max(0, body.length - 1);
6060
6116
  body[lastLineIndex] = renderQueryOnBottomLine(body[lastLineIndex] ?? "", state.width, query);
6061
6117
  return { body };
@@ -6173,7 +6229,11 @@ const expectResponse = (response, type) => {
6173
6229
  return response;
6174
6230
  };
6175
6231
  const runPopupJob = async (state, options) => {
6176
- const overlayRenderer = createOverlayRenderer(state.displayLines);
6232
+ let overlayRenderer = null;
6233
+ const getOverlayRenderer = () => {
6234
+ overlayRenderer ??= createOverlayRenderer(state.displayLines);
6235
+ return overlayRenderer;
6236
+ };
6177
6237
  let query = "";
6178
6238
  let previousHints = /* @__PURE__ */ new Map();
6179
6239
  let matches = [];
@@ -6181,7 +6241,7 @@ const runPopupJob = async (state, options) => {
6181
6241
  const output = process.stdout;
6182
6242
  await withRawMode(input, output, async () => {
6183
6243
  const reader = createByteReader(input);
6184
- let previousFrame = renderFrame(output, createFrame(state, query, matches, overlayRenderer));
6244
+ let previousFrame = renderFrame(output, createFrame(state, query, matches));
6185
6245
  try {
6186
6246
  while (true) {
6187
6247
  const value = await reader.nextByte();
@@ -6224,7 +6284,7 @@ const runPopupJob = async (state, options) => {
6224
6284
  }
6225
6285
  matches = query.length === 0 ? [] : await options.onMatch(query, previousHints);
6226
6286
  previousHints = new Map(matches.map((target) => [createTargetKey(target), target.hint]));
6227
- previousFrame = renderFrame(output, createFrame(state, query, matches, overlayRenderer), previousFrame);
6287
+ previousFrame = renderFrame(output, createFrame(state, query, matches, matches.length > 0 ? getOverlayRenderer() : void 0), previousFrame);
6228
6288
  }
6229
6289
  } finally {
6230
6290
  reader.close();
@@ -6542,7 +6602,7 @@ const composeDisplayLines = (panes, width, height, borderLines) => {
6542
6602
  for (const pane of panes) pane.displayLines.forEach((line, lineIndex) => {
6543
6603
  const row = rows[pane.top + lineIndex];
6544
6604
  if (!row) return;
6545
- createStyledDisplayCells(line).forEach((cell, cellIndex) => {
6605
+ createCompactStyledDisplayCells(line).forEach((cell, cellIndex) => {
6546
6606
  const column = pane.left + cellIndex;
6547
6607
  if (column < 0 || column >= row.length) return;
6548
6608
  row[column] = cell;
@@ -6581,10 +6641,9 @@ const buildAllPaneState = async (tmux, pane, paneId, clientTty) => {
6581
6641
  right: Number.NEGATIVE_INFINITY,
6582
6642
  bottom: Number.NEGATIVE_INFINITY
6583
6643
  });
6584
- const snapshots = [];
6585
- for (const item of panes) {
6644
+ const snapshots = await Promise.all(panes.map(async (item) => {
6586
6645
  const capture = fitCaptureToHeight(await capturePane(tmux, item.paneId), item.height);
6587
- snapshots.push({
6646
+ return {
6588
6647
  paneId: item.paneId,
6589
6648
  inCopyMode: item.inCopyMode,
6590
6649
  width: item.width,
@@ -6593,8 +6652,8 @@ const buildAllPaneState = async (tmux, pane, paneId, clientTty) => {
6593
6652
  top: item.top - bounds.top,
6594
6653
  plainLines: capture.lines,
6595
6654
  displayLines: capture.displayLines
6596
- });
6597
- }
6655
+ };
6656
+ }));
6598
6657
  const width = Math.max(0, bounds.right - bounds.left);
6599
6658
  const height = Math.max(0, bounds.bottom - bounds.top);
6600
6659
  const x = buildPopupRelativePosition("x", bounds.left);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-fuzzy-motion",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Fuzzy hint motion for tmux copy-mode",
5
5
  "type": "module",
6
6
  "files": [