Skip to content

Commit

Permalink
Use line search, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
klembot committed Mar 1, 2025
1 parent 71af894 commit c4afda2
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 32 deletions.
1 change: 0 additions & 1 deletion src/components/control/code-area/code-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
import 'codemirror/addon/display/placeholder';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/search/searchcursor';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/css/css';
import 'codemirror/mode/javascript/javascript';
Expand Down
82 changes: 66 additions & 16 deletions src/store/__tests__/use-codemirror-passage-hints.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,9 @@ describe('useCodeMirrorPassageHints()', () => {

it('returns a list of passages whose names match the word at the cursor', () => {
const fakeEditor = {
findWordAt: () => ({anchor: 0, head: 0}),
getCursor: jest.fn(),
getRange: () => 'a',
showHint: jest.fn(),
getDoc: () => ({ getSearchCursor: () => ({
findPrevious: jest.fn(),
from: () => ({ch: 0})
})})
getCursor: jest.fn(() => ({from: 0, line: 0, to: 0})),
getLine: jest.fn(() => 'a'),
showHint: jest.fn()
};
const story = fakeStory(3);

Expand All @@ -33,14 +28,9 @@ describe('useCodeMirrorPassageHints()', () => {

it('is case-insensitive', () => {
const fakeEditor = {
findWordAt: () => ({anchor: 0, head: 0}),
getCursor: jest.fn(),
getRange: () => 'a',
showHint: jest.fn(),
getDoc: () => ({ getSearchCursor: () => ({
findPrevious: jest.fn(),
from: () => ({ch: 0})
})})
getCursor: jest.fn(() => ({from: 0, line: 0, to: 0})),
getLine: jest.fn(() => 'a'),
showHint: jest.fn()
};
const story = fakeStory(3);

Expand All @@ -56,4 +46,64 @@ describe('useCodeMirrorPassageHints()', () => {
'bAA'
]);
});

it('completes passages using spaces if entered', () => {
const fakeEditor = {
getCursor: jest.fn(() => ({from: 0, line: 0, to: 0})),
getLine: jest.fn(() => 'aaa b'),
showHint: jest.fn()
};
const story = fakeStory(3);

story.passages[0].name = 'aaa bbb';
story.passages[1].name = 'ccc ddd';
story.passages[2].name = 'eee fff';

const {result} = renderHook(() => useCodeMirrorPassageHints(story));

result.current(fakeEditor as any);
expect(fakeEditor.showHint.mock.calls[0][0].hint().list).toEqual([
'aaa bbb'
]);
});

it('completes passages using spaces if entered', () => {
const fakeEditor = {
getCursor: jest.fn(() => ({from: 0, line: 0, to: 0})),
getLine: jest.fn(() => 'aaa b'),
showHint: jest.fn()
};
const story = fakeStory(3);

story.passages[0].name = 'aaa bbb';
story.passages[1].name = 'ccc ddd';
story.passages[2].name = 'eee fff';

const {result} = renderHook(() => useCodeMirrorPassageHints(story));

result.current(fakeEditor as any);
expect(fakeEditor.showHint.mock.calls[0][0].hint().list).toEqual([
'aaa bbb'
]);
});

it('considers characters entered after the [ on the same line, if present', () => {
const fakeEditor = {
getCursor: jest.fn(() => ({from: 6, line: 0, to: 6})),
getLine: jest.fn(() => 'aaa [[cc'),
showHint: jest.fn()
};
const story = fakeStory(3);

story.passages[0].name = 'aaa bbb';
story.passages[1].name = 'ccc ddd';
story.passages[2].name = 'eee fff';

const {result} = renderHook(() => useCodeMirrorPassageHints(story));

result.current(fakeEditor as any);
expect(fakeEditor.showHint.mock.calls[0][0].hint().list).toEqual([
'ccc ddd'
]);
});
});
33 changes: 18 additions & 15 deletions src/store/use-codemirror-passage-hints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,32 @@ export function useCodeMirrorPassageHints(story: Story) {
completeSingle: false,
closeCharacters: /\]/,
hint() {
// Get the current cursor position and line content.

const doc = editor.getDoc();
const cursor = editor.getCursor();
const wordRange = editor.findWordAt(cursor);
const linkCursor = doc.getSearchCursor(/\[\[|->|\|/,cursor);
const linkMatch = linkCursor.findPrevious();
const linkDelim = Array.isArray(linkMatch) ? linkMatch[0] : '';
const linkStart = linkCursor.from();
linkStart.ch += linkDelim.length;
const word = editor
.getRange(linkStart, cursor)
.toLowerCase();
const cursor = editor.getCursor();
const line = editor.getLine(cursor.line);
const from = {...cursor};
const to = {...cursor};

// Expand the range to the first `[` before the cursor. lastIndexOf()
// will either give us -1, if there was no match, or the first
// bracket. In either case, we want to add one so that it either
// points to the start of the line, or the first character after the
// match. e.g. `[passage name` becomes `passage name`.

from.ch = line.lastIndexOf('[', from.ch) + 1;

const candidate = line.substring(from.ch, to.ch).toLowerCase();
const comps = {
from,
to,
list: story.passages.reduce<string[]>((result, passage) => {
if (passage.name.toLowerCase().includes(word)) {
if (passage.name.toLowerCase().includes(candidate)) {
return [...result, passage.name];
}

return result;
}, []),
from: linkStart,
to: wordRange.head
}, [])
};

CodeMirror.on(comps, 'pick', () => {
Expand Down

0 comments on commit c4afda2

Please sign in to comment.