feat: environment variable syntax highlighting

This commit is contained in:
Anoop M D 2023-01-21 18:12:34 +05:30
parent d3d1e47950
commit d165a04377
6 changed files with 246 additions and 3 deletions

View File

@ -25,6 +25,17 @@ const StyledWrapper = styled.div`
font-weight: 400;
}
}
.tooltip {
position: absolute;
top: 0;
left: 0;
background: #fff;
border: 1px solid #ccc;
border-radius: 3px;
padding: 5px;
font-size: 12px;
z-index: 100;
}
.cm-variable-valid{color: green}

View File

@ -24,6 +24,9 @@ class SingleLineEditor extends Component {
lineNumbers: false,
autofocus: true,
mode: "brunovariables",
brunoVarInfo: {
variables: getEnvironmentVariables(this.props.collection),
},
extraKeys: {
"Enter": () => {
if (this.props.onRun) {
@ -65,8 +68,7 @@ class SingleLineEditor extends Component {
'Tab': () => {}
},
});
this.editor.setValue(this.props.value)
this.editor.setValue(this.props.value);
this.editor.on('change', (cm) => {
this.props.onChange(cm.getValue());
});
@ -76,6 +78,7 @@ class SingleLineEditor extends Component {
componentDidUpdate(prevProps) {
let variables = getEnvironmentVariables(this.props.collection);
if (!isEqual(variables, this.variables)) {
this.editor.options.brunoVarInfo.variables = variables;
this.addOverlay();
}
}

View File

@ -31,6 +31,8 @@ if (!SERVER_RENDERED) {
require('codemirror-graphql/info');
require('codemirror-graphql/jump');
require('codemirror-graphql/mode');
require('utils/codemirror/brunoVarInfo');
}
export default function Main() {

View File

@ -12,6 +12,7 @@ import '../styles/globals.css';
import 'tailwindcss/dist/tailwind.min.css';
import 'codemirror/lib/codemirror.css';
import 'graphiql/graphiql.min.css';
import 'utils/codemirror/brunoVarInfo.css';
function SafeHydrate({ children }) {
return <div suppressHydrationWarning>{typeof window === 'undefined' ? null : children}</div>;

View File

@ -0,0 +1,28 @@
.CodeMirror-brunoVarInfo {
background: white;
border-radius: 2px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
box-sizing: border-box;
font-size: 13px;
line-height: 16px;
margin: 8px -8px;
max-width: 800px;
opacity: 0;
overflow: hidden;
padding: 8px 8px;
position: fixed;
transition: opacity 0.15s;
z-index: 50;
}
.CodeMirror-brunoVarInfo :first-child {
margin-top: 0;
}
.CodeMirror-brunoVarInfo :last-child {
margin-bottom: 0;
}
.CodeMirror-brunoVarInfo p {
margin: 1em 0;
}

View File

@ -0,0 +1,198 @@
/**
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file at https://github.com/graphql/codemirror-graphql/tree/v0.8.3
*/
let CodeMirror;
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
if (!SERVER_RENDERED) {
CodeMirror = require('codemirror');
const renderVarInfo = (token, options, cm, pos) => {
const str = token.string || '';
// str is of format {{variableName}}, extract variableName
const variableName = str.substring(2, str.length - 2);
// get the value of the variable
const variableValue = options.variables[variableName] || '';
const into = document.createElement('div');
const descriptionDiv = document.createElement('div');
descriptionDiv.className = 'info-description';
descriptionDiv.appendChild(document.createTextNode(variableValue));
into.appendChild(descriptionDiv);
return into;
};
CodeMirror.defineOption('brunoVarInfo', false, function(cm, options, old) {
if (old && old !== CodeMirror.Init) {
const oldOnMouseOver = cm.state.brunoVarInfo.onMouseOver;
CodeMirror.off(cm.getWrapperElement(), 'mouseover', oldOnMouseOver);
clearTimeout(cm.state.brunoVarInfo.hoverTimeout);
delete cm.state.brunoVarInfo;
}
if (options) {
const state = (cm.state.brunoVarInfo = createState(options));
state.onMouseOver = onMouseOver.bind(null, cm);
CodeMirror.on(cm.getWrapperElement(), 'mouseover', state.onMouseOver);
}
});
function createState(options) {
return {
options:
options instanceof Function
? {render: options}
: options === true ? {} : options,
};
}
function getHoverTime(cm) {
const options = cm.state.brunoVarInfo.options;
return (options && options.hoverTime) || 50;
}
function onMouseOver(cm, e) {
const state = cm.state.brunoVarInfo;
const target = e.target || e.srcElement;
if (target.nodeName !== 'SPAN' || state.hoverTimeout !== undefined) {
return;
}
if(target.className !== 'cm-variable-valid') {
return;
}
const box = target.getBoundingClientRect();
const hoverTime = getHoverTime(cm);
state.hoverTimeout = setTimeout(onHover, hoverTime);
const onMouseMove = function() {
clearTimeout(state.hoverTimeout);
state.hoverTimeout = setTimeout(onHover, hoverTime);
};
const onMouseOut = function() {
CodeMirror.off(document, 'mousemove', onMouseMove);
CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut);
clearTimeout(state.hoverTimeout);
state.hoverTimeout = undefined;
};
const onHover = function() {
CodeMirror.off(document, 'mousemove', onMouseMove);
CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut);
state.hoverTimeout = undefined;
onMouseHover(cm, box);
};
CodeMirror.on(document, 'mousemove', onMouseMove);
CodeMirror.on(cm.getWrapperElement(), 'mouseout', onMouseOut);
}
function onMouseHover(cm, box) {
const pos = cm.coordsChar({
left: (box.left + box.right) / 2,
top: (box.top + box.bottom) / 2,
});
const state = cm.state.brunoVarInfo;
const options = state.options;
const token = cm.getTokenAt(pos, true);
if (token) {
const brunoVarInfo = renderVarInfo(token, options, cm, pos);
if (brunoVarInfo) {
showPopup(cm, box, brunoVarInfo);
}
}
}
function showPopup(cm, box, brunoVarInfo) {
const popup = document.createElement('div');
popup.className = 'CodeMirror-brunoVarInfo';
popup.appendChild(brunoVarInfo);
document.body.appendChild(popup);
const popupBox = popup.getBoundingClientRect();
const popupStyle = popup.currentStyle || window.getComputedStyle(popup);
const popupWidth =
popupBox.right -
popupBox.left +
parseFloat(popupStyle.marginLeft) +
parseFloat(popupStyle.marginRight);
const popupHeight =
popupBox.bottom -
popupBox.top +
parseFloat(popupStyle.marginTop) +
parseFloat(popupStyle.marginBottom);
let topPos = box.bottom;
if (
popupHeight > window.innerHeight - box.bottom - 15 &&
box.top > window.innerHeight - box.bottom
) {
topPos = box.top - popupHeight;
}
if (topPos < 0) {
topPos = box.bottom;
}
// make popup appear on top of cursor
if (topPos > 70) {
topPos = topPos - 70;
}
let leftPos = Math.max(0, window.innerWidth - popupWidth - 15);
if (leftPos > box.left) {
leftPos = box.left;
}
popup.style.opacity = 1;
popup.style.top = topPos + 'px';
popup.style.left = leftPos + 'px';
let popupTimeout;
const onMouseOverPopup = function() {
clearTimeout(popupTimeout);
};
const onMouseOut = function() {
clearTimeout(popupTimeout);
popupTimeout = setTimeout(hidePopup, 200);
};
const hidePopup = function() {
CodeMirror.off(popup, 'mouseover', onMouseOverPopup);
CodeMirror.off(popup, 'mouseout', onMouseOut);
CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut);
if (popup.style.opacity) {
popup.style.opacity = 0;
setTimeout(function() {
if (popup.parentNode) {
popup.parentNode.removeChild(popup);
}
}, 600);
} else if (popup.parentNode) {
popup.parentNode.removeChild(popup);
}
};
CodeMirror.on(popup, 'mouseover', onMouseOverPopup);
CodeMirror.on(popup, 'mouseout', onMouseOut);
CodeMirror.on(cm.getWrapperElement(), 'mouseout', onMouseOut);
}
}