dianping/cat · error · Error

Search feature not available. Requires searchcursor.js or…

Error message

Search feature not available. Requires searchcursor.js or any other getSearchCursor implementation.

What it means

Thrown by the vim keybinding's `:substitute` (`:s`) implementation when `cm.getSearchCursor` is not defined on the CodeMirror instance. The substitute command needs to iterate matches line by line, and CodeMirror ships that capability in a separate addon (`search/searchcursor.js`); without it the core editor has no search API. The code checks for the capability up front and fails fast instead of silently doing nothing.

Solutions

  1. Include the searchcursor addon before the vim keybinding: `<script src="codemirror/addon/search/searchcursor.js">` (or `import 'codemirror/addon/search/searchcursor'`).
  2. If importing modules, ensure the addon import happens in the same bundle/page where the vim keymap is active.
  3. Alternatively provide any `getSearchCursor(query, pos, caseFold)` implementation on the CodeMirror prototype before vim commands run.
  4. As a last resort, disable `:substitute`/`:g` in your command handling if search is intentionally excluded.

Example fix

// before
import CodeMirror from 'codemirror';
import 'codemirror/keymap/vim.js'; // :%s/.../ throws 'Search feature not available'

// after
import CodeMirror from 'codemirror';
import 'codemirror/addon/search/searchcursor.js'; // provides cm.getSearchCursor
import 'codemirror/keymap/vim.js';
Defensive patterns

Strategy: type-guard

Validate before calling

function searchAvailable(cm) {
  return typeof cm.getSearchCursor === 'function';
}
if (!searchAvailable(cm)) {
  document.querySelector('script#searchcursor') || warn(':s/:g disabled — load searchcursor.js');
}

Type guard

function hasSearchCursor(cm) {
  return typeof cm.getSearchCursor === 'function';
}

Try / catch

try {
  CodeMirror.Vim.handleEx(cm, ':%s/old/new/g');
} catch (e) {
  if (/searchcursor/.test(e.message)) { loadScript('addon/search/searchcursor.js').then(retry); return; }
  throw e;
}

Prevention

When it happens

Trigger: Loading `keybinding-vim.js` without first (or ever) loading the `searchcursor.js` addon, then running `:%s/foo/bar/g`, `:s/x/y`, or `:g/pat/cmd` (global also drives substitute); building a custom CodeMirror bundle that omits the search addon; using a third-party editor wrapper that only includes the vim keymap.

Common situations: Custom bundlers (webpack/esbuild tree-shaking) stripping the addon; pages that copy keybinding-vim.js alone out of the CodeMirror distribution; upgrading CodeMirror where addons moved to separate module paths and the import was not migrated.

Related errors


AI-assisted analysis of dianping/cat@e815e74d4c (2026-08-14). Data as JSON: /api/errors/48360a99e49a98ec. Report an issue: GitHub.

Appendix: source

Thrown at cat-home/src/main/webapp/assets/js/editor/keybinding-vim.js:4677

        if (!cmd) {
          showConfirm(cm, content);
          return;
        }
        var index = 0;
        var nextCommand = function() {
          if (index < matchedLines.length) {
            var command = matchedLines[index] + cmd;
            exCommandDispatcher.processCommand(cm, command, {
              callback: nextCommand
            });
          }
          index++;
        };
        nextCommand();
      },
      substitute: function(cm, params) {
        if (!cm.getSearchCursor) {
          throw new Error('Search feature not available. Requires searchcursor.js or ' +
              'any other getSearchCursor implementation.');
        }
        var argString = params.argString;
        var tokens = argString ? splitBySlash(argString) : [];
        var regexPart, replacePart = '', trailing, flagsPart, count;
        var confirm = false; // Whether to confirm each replace.
        var global = false; // True to replace all instances on a line, false to replace only 1.
        if (tokens.length) {
          regexPart = tokens[0];
          replacePart = tokens[1];
          if (replacePart !== undefined) {
            if (getOption('pcre')) {
              replacePart = unescapeRegexReplace(replacePart);
            } else {
              replacePart = translateRegexReplace(replacePart);
            }
            vimGlobalState.lastSubstituteReplacePart = replacePart;
          }

View on GitHub (pinned to e815e74d4c)