{"record":{"id":"48360a99e49a98ec","repo":"dianping/cat","slug":"search-feature-not-available-requires-searchcurso","errorCode":null,"errorMessage":"Search feature not available. Requires searchcursor.js or any other getSearchCursor implementation.","messagePattern":"Search feature not available\\. Requires searchcursor\\.js or any other getSearchCursor implementation\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cat-home/src/main/webapp/assets/js/editor/keybinding-vim.js","lineNumber":4677,"sourceCode":"        if (!cmd) {\n          showConfirm(cm, content);\n          return;\n        }\n        var index = 0;\n        var nextCommand = function() {\n          if (index < matchedLines.length) {\n            var command = matchedLines[index] + cmd;\n            exCommandDispatcher.processCommand(cm, command, {\n              callback: nextCommand\n            });\n          }\n          index++;\n        };\n        nextCommand();\n      },\n      substitute: function(cm, params) {\n        if (!cm.getSearchCursor) {\n          throw new Error('Search feature not available. Requires searchcursor.js or ' +\n              'any other getSearchCursor implementation.');\n        }\n        var argString = params.argString;\n        var tokens = argString ? splitBySlash(argString) : [];\n        var regexPart, replacePart = '', trailing, flagsPart, count;\n        var confirm = false; // Whether to confirm each replace.\n        var global = false; // True to replace all instances on a line, false to replace only 1.\n        if (tokens.length) {\n          regexPart = tokens[0];\n          replacePart = tokens[1];\n          if (replacePart !== undefined) {\n            if (getOption('pcre')) {\n              replacePart = unescapeRegexReplace(replacePart);\n            } else {\n              replacePart = translateRegexReplace(replacePart);\n            }\n            vimGlobalState.lastSubstituteReplacePart = replacePart;\n          }","sourceCodeStart":4659,"sourceCodeEnd":4695,"githubUrl":"https://github.com/dianping/cat/blob/e815e74d4c2dd74edac831241f1253fcc7d25381/cat-home/src/main/webapp/assets/js/editor/keybinding-vim.js#L4659-L4695","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Include the searchcursor addon before the vim keybinding: `<script src=\"codemirror/addon/search/searchcursor.js\">` (or `import 'codemirror/addon/search/searchcursor'`).","If importing modules, ensure the addon import happens in the same bundle/page where the vim keymap is active.","Alternatively provide any `getSearchCursor(query, pos, caseFold)` implementation on the CodeMirror prototype before vim commands run.","As a last resort, disable `:substitute`/`:g` in your command handling if search is intentionally excluded."],"exampleFix":"// before\nimport CodeMirror from 'codemirror';\nimport 'codemirror/keymap/vim.js'; // :%s/.../ throws 'Search feature not available'\n\n// after\nimport CodeMirror from 'codemirror';\nimport 'codemirror/addon/search/searchcursor.js'; // provides cm.getSearchCursor\nimport 'codemirror/keymap/vim.js';","handlingStrategy":"type-guard","validationCode":"function searchAvailable(cm) {\n  return typeof cm.getSearchCursor === 'function';\n}\nif (!searchAvailable(cm)) {\n  document.querySelector('script#searchcursor') || warn(':s/:g disabled — load searchcursor.js');\n}","typeGuard":"function hasSearchCursor(cm) {\n  return typeof cm.getSearchCursor === 'function';\n}","tryCatchPattern":"try {\n  CodeMirror.Vim.handleEx(cm, ':%s/old/new/g');\n} catch (e) {\n  if (/searchcursor/.test(e.message)) { loadScript('addon/search/searchcursor.js').then(retry); return; }\n  throw e;\n}","preventionTips":["Always load the searchcursor addon in the same page/bundle as keybinding-vim.js.","In bundler setups, import 'codemirror/addon/search/searchcursor' explicitly so tree-shaking cannot drop it.","Feature-detect cm.getSearchCursor before enabling :substitute/:global UI affordances.","Keep addons and the vim keymap from the same CodeMirror version."],"tags":["vim","codemirror","searchcursor","addon","substitute"],"backgroundTag":null,"analyzedSha":"e815e74d4c2dd74edac831241f1253fcc7d25381","analyzedAt":"2026-08-14T14:22:34.512Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}