can1357/oh-my-pi · error

Operation needs visible current text.

Error message

Operation ${operationNumber} needs visible current text.

What it means

After tokenizing the pattern and stripping leading/trailing gaps, no literal tokens remain — the pattern is made entirely of gap markers (and selections). A pattern with no visible text cannot anchor to any location in the file, so the library throws instead of matching arbitrary content.

Solutions

  1. Add at least one literal line of code from the target region as an anchor.
  2. If you want to match from a point to the end, keep a leading anchor literal before the `...` gap.
  3. For pure insertion between known lines, include both surrounding lines plus the gap.

Example fix

// before
const pattern = "...";
// after
const pattern = "const total = sum(items);\n...";
Defensive patterns

Strategy: validation

Validate before calling

const literalsOnly = pattern.replace(/«|»|\.\.\./g, "").trim();
if (literalsOnly === "") throw new Error("pattern is gaps-only; add a literal anchor line");

Type guard

function hasLiteralAnchor(pattern: string): boolean {
  return pattern.replace(/«|»|\.\.\./g, "").trim().length > 0;
}

Try / catch

try {
  await applySloppyEdit({ pattern });
} catch (err) {
  if (err instanceof Error && err.message.includes("needs visible current text")) {
    // insert an anchor line from the target region into the pattern
  }
  throw err;
}

Prevention

When it happens

Trigger: A pattern consisting solely of `...` (with optional selection markers), or one where every line was inside gaps. Detected in the compiled-pattern builder when `tokens.filter(kind === "literal")` is empty after trailing gaps are popped.

Common situations: Trying to 'replace everything between anchors' by writing only gaps and forgetting at least one concrete anchor line; a template that interpolates an empty extracted snippet into the pattern; deleting all the literal lines from a previously working pattern during refactoring.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/337361d47c258f57. Report an issue: GitHub.

Appendix: source

Thrown at packages/coding-agent/src/edit/sloppy.ts:1566

			selectionRawOffsets.push(index);
		} else {
			literal += character;
		}
		index += character.length;
	}
	flushLiteral();

	let strippedLeading = 0;
	while (tokens[0]?.kind === "gap") {
		tokens.shift();
		strippedLeading++;
	}
	while (tokens.at(-1)?.kind === "gap") tokens.pop();
	for (let index = 0; index < selectionBoundaries.length; index++) {
		selectionBoundaries[index] = Math.max(0, Math.min(tokens.length, selectionBoundaries[index] - strippedLeading));
	}
	const literals = tokens.filter((token): token is LiteralToken => token.kind === "literal");
	if (literals.length === 0) throw new Error(`Operation ${operationNumber} needs visible current text.`);
	// Only punctuation-only anchors (`}`, `};`, `);`) are genuinely too generic:
	// they match everywhere and their candidate lists are noise. Any identifier
	// text — however short (`id`, `avlue`) — is a legitimate anchor; uniqueness
	// (or an explicit `«*`) decides whether it applies, not its length.
	const hasIdentifierText = literals.some(token => /[\p{L}\p{N}_$]/u.test(token.normalized));
	if (!hasIdentifierText) {
		throw new Error(`Operation ${operationNumber} pattern is too generic; include a distinctive name or statement.`);
	}

	const emptyDoubleSelection = selectionBoundaries.length === 2 && selectionBoundaries[0] === selectionBoundaries[1];
	const insertion = selectionBoundaries.length === 1 || emptyDoubleSelection;
	const explicitSingleSelection = selectionBoundaries.length === 2 && !emptyDoubleSelection;
	const selectionStart = insertion || explicitSingleSelection ? selectionBoundaries[0] : 0;
	const selectionEnd = insertion ? selectionStart : explicitSingleSelection ? selectionBoundaries[1] : tokens.length;
	const selectionPairs =
		selectionBoundaries.length > 0 && selectionBoundaries.length % 2 === 0
			? Array.from({ length: selectionBoundaries.length / 2 }, (_, index) => {
					const start = selectionBoundaries[index * 2];

View on GitHub (pinned to 9690622007)