fix(forge): autocompletion Tab naturelle et fiable (BUG-055)
This commit is contained in:
@@ -347,4 +347,97 @@ function filterFiles(partial) {
|
||||
});
|
||||
}
|
||||
|
||||
export { suggest, searchWikilinks, detectContext, MERMAID_TYPES, CODE_LANGUAGES, FRONTMATTER_FIELDS, MARKDOWN_FORMAT };
|
||||
// ── Word completion (Forge « Tab » completion) ────────────────────────
|
||||
// A word character matches the same set used by the editor: letters, digits,
|
||||
// underscore, dash, dot and slash (so paths like `docs/guide` complete too).
|
||||
var _WORD_CHAR = /[\w\-.\/]/;
|
||||
|
||||
function isWordChar(ch) {
|
||||
return !!ch && _WORD_CHAR.test(ch);
|
||||
}
|
||||
|
||||
/** Return the word fragment immediately before the cursor. */
|
||||
function getWordFragment(text, cursorPos) {
|
||||
var start = cursorPos;
|
||||
while (start > 0 && isWordChar(text.charAt(start - 1))) start--;
|
||||
return { start: start, fragment: text.slice(start, cursorPos) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect unique words of `text` that start with `fragment` (case-insensitive).
|
||||
* The occurrence currently being typed at the cursor is ignored. `limit` caps
|
||||
* the number of candidates (0 = no limit).
|
||||
*/
|
||||
function findWordCompletions(text, cursorPos, fragment, limit) {
|
||||
if (!fragment || fragment.length < 2) return [];
|
||||
var lower = fragment.toLowerCase();
|
||||
var re = /[\w\-.\/]+/g;
|
||||
var seen = Object.create(null);
|
||||
var out = [];
|
||||
var m;
|
||||
while ((m = re.exec(text)) !== null) {
|
||||
var word = m[0];
|
||||
if (word.length <= fragment.length) continue;
|
||||
if (word.toLowerCase().indexOf(lower) !== 0) continue;
|
||||
// Skip the word currently being typed (it starts exactly at the cursor).
|
||||
if (m.index === cursorPos - fragment.length) continue;
|
||||
var key = word.toLowerCase();
|
||||
if (seen[key]) continue;
|
||||
seen[key] = true;
|
||||
out.push(word);
|
||||
if (limit && out.length >= limit) break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up a raw AI prediction before displaying/inserting it.
|
||||
*
|
||||
* - strips the echoed input prefix (mid-word or full context),
|
||||
* - for a mid-word completion, keeps only the first token so a word completion
|
||||
* can never introduce a stray space,
|
||||
* - trims any remaining leading whitespace.
|
||||
*/
|
||||
function normalizeGhost(raw, inputText, midWord) {
|
||||
var p = raw == null ? '' : String(raw).trim();
|
||||
if (!p) return '';
|
||||
if (inputText) {
|
||||
var inp = String(inputText);
|
||||
if (midWord && p.toLowerCase().indexOf(inp.toLowerCase()) === 0) {
|
||||
p = p.slice(inp.length);
|
||||
} else if (!midWord && p.indexOf(inp) === 0) {
|
||||
p = p.slice(inp.length);
|
||||
}
|
||||
}
|
||||
if (midWord) p = p.split(/\s+/)[0];
|
||||
return p.replace(/^\s+/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide what the « Tab » key should do. Kept pure so the editor and the tests
|
||||
* share the exact same priority rules.
|
||||
*
|
||||
* dropdown → an autocomplete list is open: accept the highlighted item
|
||||
* ghost → an AI inline prediction is displayed: accept it
|
||||
* dedent → Shift+Tab: remove indentation
|
||||
* word → a single document word matches: insert its suffix
|
||||
* word-list → several words match: open the suggestion list
|
||||
* indent → default: indent the current line
|
||||
*/
|
||||
function chooseTabAction(state) {
|
||||
state = state || {};
|
||||
if (state.dropdownOpen) return 'dropdown';
|
||||
if (state.ghost) return 'ghost';
|
||||
if (state.shiftKey) return 'dedent';
|
||||
if (state.hasSelection) return 'indent';
|
||||
var candidates = state.candidates || [];
|
||||
if (candidates.length === 1) return 'word';
|
||||
if (candidates.length > 1) return 'word-list';
|
||||
return 'indent';
|
||||
}
|
||||
|
||||
export {
|
||||
suggest, searchWikilinks, detectContext,
|
||||
MERMAID_TYPES, CODE_LANGUAGES, FRONTMATTER_FIELDS, MARKDOWN_FORMAT,
|
||||
isWordChar, getWordFragment, findWordCompletions, normalizeGhost, chooseTabAction,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user