UNPKG

codemirror-graphql

Version:
161 lines (133 loc) 4.26 kB
/** * Copyright (c) 2021 GraphQL Contributors * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ import CodeMirror from 'codemirror'; import { GraphQLJumpOptions } from '../jump'; CodeMirror.defineOption( 'jump', false, ( cm: CodeMirror.Editor, options: GraphQLJumpOptions, old?: GraphQLJumpOptions, ) => { if (old && old !== CodeMirror.Init) { const oldOnMouseOver = cm.state.jump.onMouseOver; CodeMirror.off(cm.getWrapperElement(), 'mouseover', oldOnMouseOver); const oldOnMouseOut = cm.state.jump.onMouseOut; CodeMirror.off(cm.getWrapperElement(), 'mouseout', oldOnMouseOut); CodeMirror.off(document, 'keydown', cm.state.jump.onKeyDown); delete cm.state.jump; } if (options) { const state = (cm.state.jump = { options, onMouseOver: onMouseOver.bind(null, cm), onMouseOut: onMouseOut.bind(null, cm), onKeyDown: onKeyDown.bind(null, cm), }); CodeMirror.on(cm.getWrapperElement(), 'mouseover', state.onMouseOver); CodeMirror.on(cm.getWrapperElement(), 'mouseout', state.onMouseOut); CodeMirror.on(document, 'keydown', state.onKeyDown); } }, ); function onMouseOver(cm: CodeMirror.Editor, event: MouseEvent) { const target = event.target || event.srcElement; if (!(target instanceof HTMLElement)) { return; } if (target?.nodeName !== 'SPAN') { return; } const box = target.getBoundingClientRect(); const cursor = { left: (box.left + box.right) / 2, top: (box.top + box.bottom) / 2, }; cm.state.jump.cursor = cursor; if (cm.state.jump.isHoldingModifier) { enableJumpMode(cm); } } function onMouseOut(cm: CodeMirror.Editor) { if (!cm.state.jump.isHoldingModifier && cm.state.jump.cursor) { cm.state.jump.cursor = null; return; } if (cm.state.jump.isHoldingModifier && cm.state.jump.marker) { disableJumpMode(cm); } } function onKeyDown(cm: CodeMirror.Editor, event: KeyboardEvent) { if (cm.state.jump.isHoldingModifier || !isJumpModifier(event.key)) { return; } cm.state.jump.isHoldingModifier = true; if (cm.state.jump.cursor) { enableJumpMode(cm); } const onKeyUp = (upEvent: KeyboardEvent) => { if (upEvent.code !== event.code) { return; } cm.state.jump.isHoldingModifier = false; if (cm.state.jump.marker) { disableJumpMode(cm); } CodeMirror.off(document, 'keyup', onKeyUp); CodeMirror.off(document, 'click', onClick); cm.off('mousedown', onMouseDown); }; const onClick = (clickEvent: MouseEvent) => { const { destination, options } = cm.state.jump; if (destination) { options.onClick(destination, clickEvent); } }; const onMouseDown = (_: any, downEvent: MouseEvent) => { if (cm.state.jump.destination) { (downEvent as any).codemirrorIgnore = true; } }; CodeMirror.on(document, 'keyup', onKeyUp); CodeMirror.on(document, 'click', onClick); cm.on('mousedown', onMouseDown); } const isMac = typeof navigator !== 'undefined' && navigator.userAgent.includes('Mac'); function isJumpModifier(key: string) { return key === (isMac ? 'Meta' : 'Control'); } function enableJumpMode(cm: CodeMirror.Editor) { if (cm.state.jump.marker) { return; } const { cursor, options } = cm.state.jump; const pos = cm.coordsChar(cursor); const token = cm.getTokenAt(pos, true); const getDestination = options.getDestination || cm.getHelper(pos, 'jump'); if (getDestination) { const destination = getDestination(token, options, cm); if (destination) { const marker = cm.markText( { line: pos.line, ch: token.start }, { line: pos.line, ch: token.end }, { className: 'CodeMirror-jump-token' }, ); cm.state.jump.marker = marker; cm.state.jump.destination = destination; } } } function disableJumpMode(cm: CodeMirror.Editor) { const { marker } = cm.state.jump; cm.state.jump.marker = null; cm.state.jump.destination = null; marker.clear(); }