diff --git a/src/hoverifier.ts b/src/hoverifier.ts index 8b5e2c57..e0cfa2b3 100644 --- a/src/hoverifier.ts +++ b/src/hoverifier.ts @@ -87,6 +87,11 @@ export interface HoverifierOptions { * Whether or not hover tooltips can be pinned. */ pinningEnabled: boolean + + /** + * Whether or not code views need to be tokenized. Defaults to true. + */ + tokenize?: boolean } /** @@ -349,6 +354,7 @@ export function createHoverifier({ getHover, getActions, pinningEnabled, + tokenize = true, }: HoverifierOptions): Hoverifier { // Internal state that is not exposed to the caller // Shared between all hoverified code views @@ -502,7 +508,7 @@ export function createHoverifier({ if (isPosition(position)) { cell = dom.getCodeElementFromLineNumber(codeView, position.line, position.part) if (cell) { - target = findElementWithOffset(cell, position.character) + target = findElementWithOffset(cell, position.character, tokenize) if (target) { part = dom.getDiffCodePart && dom.getDiffCodePart(target) } else { @@ -573,7 +579,7 @@ export function createHoverifier({ // placed in the middle of a token. target: position && isPosition(position) - ? getTokenAtPosition(codeView, position, dom, position.part) + ? getTokenAtPosition(codeView, position, dom, position.part, tokenize) : target, ...rest, })), @@ -734,7 +740,7 @@ export function createHoverifier({ container.update({ hoveredTokenElement: undefined }) return } - const token = getTokenAtPosition(codeView, highlightedRange.start, dom, part) + const token = getTokenAtPosition(codeView, highlightedRange.start, dom, part, tokenize) container.update({ hoveredTokenElement: token }) if (!token) { return @@ -838,8 +844,10 @@ export function createHoverifier({ selectedPosition: position, }) const codeElements = getCodeElementsInRange({ codeView, position, getCodeElementFromLineNumber }) - for (const { element } of codeElements) { - convertNode(element) + if (tokenize) { + for (const { element } of codeElements) { + convertNode(element) + } } // Scroll into view if (codeElements.length > 0) { diff --git a/src/token_position.test.ts b/src/token_position.test.ts index 32a521dd..0ae2ff9c 100644 --- a/src/token_position.test.ts +++ b/src/token_position.test.ts @@ -89,7 +89,7 @@ describe('token_positions', () => { }) describe('findElementWithOffset()', () => { - it('finds the correct token', () => { + it('finds the correct token (with tokenization)', () => { const content = `${tabChar}if rv := contextGet(r, routeKey); rv != nil {` const elems = [ @@ -122,6 +122,40 @@ describe('token_positions', () => { } }) + it('finds the correct token (without tokenization)', () => { + const content = ` if rv := contextGet(r, varsKey); rv != nil {` + + // Each offset is 3 more than the corresponding offset in the + // tokenized test above because this test case comes from Bitbucket + // where tabs are converted to spaces. + // + // The '(' and ' ' tokens are absent from this test because, on + // Bitbucket, punctuation characters are not wrapped in tags and the + // current offset-finding logic can't determine the offset for such + // tokens. One way to fix that is to use the CodeMirror API + // directly. + const elems = [ + { + offset: 14, + token: 'contextGet', + }, + { + offset: 5, + token: 'if', + }, + ] + + const elem = dom.createElementFromString(content) + + for (const { offset, token } of elems) { + const tokenElem = findElementWithOffset(elem, offset, false) + + expect(tokenElem).to.not.equal(undefined) + + expect(tokenElem!.textContent).to.equal(token) + } + }) + it('returns undefined for invalid offsets', () => { const content = 'Hello, World!' diff --git a/src/token_position.ts b/src/token_position.ts index 44aa06d4..7ef158e1 100644 --- a/src/token_position.ts +++ b/src/token_position.ts @@ -252,9 +252,15 @@ export const getTextNodes = (node: Node): Node[] => { * @param codeElement the element containing syntax highlighted code * @param offset character offset (1-indexed) */ -export function findElementWithOffset(codeElement: HTMLElement, offset: number): HTMLElement | undefined { - // Without being converted first, finding the position is inaccurate - convertCodeElementIdempotent(codeElement) +export function findElementWithOffset( + codeElement: HTMLElement, + offset: number, + tokenize = true +): HTMLElement | undefined { + if (tokenize) { + // Without being converted first, finding the position is inaccurate + convertCodeElementIdempotent(codeElement) + } const textNodes = getTextNodes(codeElement) @@ -475,7 +481,8 @@ export const getTokenAtPosition = ( getCodeElementFromLineNumber, isFirstCharacterDiffIndicator, }: Pick, - part?: DiffPart + part?: DiffPart, + tokenize = true, ): HTMLElement | undefined => { const codeElement = getCodeElementFromLineNumber(codeView, line, part) if (!codeElement) { @@ -486,5 +493,5 @@ export const getTokenAtPosition = ( character++ } - return findElementWithOffset(codeElement, character) + return findElementWithOffset(codeElement, character, tokenize) }