mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 17:08:34 +00:00
Merge pull request #12889 from ToolJet/feat/override-codemirror-autocomplete-default-filter
Feat/override codemirror autocomplete default filter
This commit is contained in:
commit
77bf79f1c2
4 changed files with 92 additions and 51 deletions
|
|
@ -20,6 +20,7 @@ import { PreviewBox } from './PreviewBox';
|
|||
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
import { search, searchKeymap, searchPanelOpen } from '@codemirror/search';
|
||||
import { handleSearchPanel } from './SearchBox';
|
||||
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
|
||||
|
|
@ -67,7 +68,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
|
||||
const context = useContext(CodeHinterContext);
|
||||
|
||||
const { suggestionList } = createReferencesLookup(context, true);
|
||||
const { suggestionList: paramList } = createReferencesLookup(context, true);
|
||||
|
||||
const currentValueRef = useRef(initialValue);
|
||||
|
||||
|
|
@ -148,8 +149,29 @@ const MultiLineCodeEditor = (props) => {
|
|||
return suggestion.hint.includes(nearestSubstring);
|
||||
});
|
||||
|
||||
const localVariables = new Set();
|
||||
|
||||
// Traverse the syntax tree to extract variable declarations
|
||||
syntaxTree(context.state).iterate({
|
||||
enter: (node) => {
|
||||
// JavaScript: Detect variable declarations (var, let, const)
|
||||
if (node.name === 'VariableDefinition') {
|
||||
const varName = context.state.sliceDoc(node.from, node.to);
|
||||
if (varName && varName.startsWith(nearestSubstring)) localVariables.add(varName);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Convert Set to an array of completion suggestions
|
||||
const localVariableSuggestions = [...localVariables].map((varName) => ({
|
||||
hint: varName,
|
||||
type: 'variable',
|
||||
}));
|
||||
|
||||
const suggestionList = paramList.filter((paramSuggestion) => paramSuggestion.hint.includes(nearestSubstring));
|
||||
|
||||
const suggestions = generateHints(
|
||||
[...JSLangHints, ...autoSuggestionList, ...suggestionList],
|
||||
[...localVariableSuggestions, ...JSLangHints, ...autoSuggestionList, ...suggestionList],
|
||||
null,
|
||||
nearestSubstring
|
||||
).map((hint) => {
|
||||
|
|
@ -206,6 +228,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
return {
|
||||
from: context.pos,
|
||||
options: [...suggestions],
|
||||
filter: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +262,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), []);
|
||||
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [paramList]);
|
||||
const { handleTogglePopupExapand, isOpen, setIsOpen, forceUpdate } = portalProps;
|
||||
let cyLabel = paramLabel ? paramLabel.toLowerCase().trim().replace(/\s+/g, '-') : props.cyLabel;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
/* eslint-disable import/no-unresolved */
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState, useContext } from 'react';
|
||||
import { PreviewBox } from './PreviewBox';
|
||||
import { ToolTip } from '@/Editor/Inspector/Elements/Components/ToolTip';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { camelCase, isEmpty, noop, get } from 'lodash';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { autocompletion, completionKeymap, completionStatus, acceptCompletion } from '@codemirror/autocomplete';
|
||||
import {
|
||||
autocompletion,
|
||||
completionKeymap,
|
||||
completionStatus,
|
||||
acceptCompletion,
|
||||
startCompletion,
|
||||
} from '@codemirror/autocomplete';
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { keymap } from '@codemirror/view';
|
||||
import FxButton from '../CodeBuilder/Elements/FxButton';
|
||||
|
|
@ -22,6 +28,8 @@ import CodeHinter from './CodeHinter';
|
|||
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { CodeHinterContext } from '../CodeBuilder/CodeHinterContext';
|
||||
import { createReferencesLookup } from '@/_stores/utils';
|
||||
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
|
||||
|
||||
const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => {
|
||||
|
|
@ -73,6 +81,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
|
|||
if (typeof initialValue === 'string' && (initialValue?.includes('components') || initialValue?.includes('queries'))) {
|
||||
newInitialValue = replaceIdsWithName(initialValue);
|
||||
}
|
||||
|
||||
//! Re render the component when the componentName changes as the initialValue is not updated
|
||||
|
||||
// const { variablesExposedForPreview } = useContext(EditorContext) || {};
|
||||
|
|
@ -199,9 +208,14 @@ const EditorInput = ({
|
|||
wrapperRef,
|
||||
showSuggestions,
|
||||
}) => {
|
||||
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
|
||||
const codeHinterContext = useContext(CodeHinterContext);
|
||||
const { suggestionList: paramHints } = createReferencesLookup(codeHinterContext, true);
|
||||
|
||||
const getSuggestions = useStore((state) => state.getSuggestions, shallow);
|
||||
const [codeMirrorView, setCodeMirrorView] = useState(undefined);
|
||||
|
||||
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
|
||||
|
||||
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onBlurUpdate, currentValue, 'singleline');
|
||||
|
||||
const isInsideQueryManager = useMemo(
|
||||
|
|
@ -209,16 +223,16 @@ const EditorInput = ({
|
|||
[wrapperRef.current]
|
||||
);
|
||||
function autoCompleteExtensionConfig(context) {
|
||||
const hints = getSuggestions();
|
||||
const hintsWithoutParamHints = getSuggestions();
|
||||
const serverHints = getServerSideGlobalSuggestions(isInsideQueryManager);
|
||||
|
||||
const allHints = {
|
||||
...hints,
|
||||
appHints: [...hints.appHints, ...serverHints],
|
||||
};
|
||||
|
||||
let word = context.matchBefore(/\w*/);
|
||||
|
||||
const hints = {
|
||||
...hintsWithoutParamHints,
|
||||
appHints: [...hintsWithoutParamHints.appHints, ...serverHints, ...paramHints],
|
||||
};
|
||||
|
||||
const totalReferences = (context.state.doc.toString().match(/{{/g) || []).length;
|
||||
|
||||
let queryInput = context.state.doc.toString();
|
||||
|
|
@ -247,17 +261,18 @@ const EditorInput = ({
|
|||
queryInput = '{{' + currentWord + '}}';
|
||||
}
|
||||
|
||||
let completions = getAutocompletion(queryInput, validationType, allHints, totalReferences, originalQueryInput);
|
||||
let completions = getAutocompletion(queryInput, validationType, hints, totalReferences, originalQueryInput);
|
||||
|
||||
return {
|
||||
from: word.from,
|
||||
options: completions,
|
||||
validFor: /^\{\{.*\}\}$/,
|
||||
filter: false,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager]);
|
||||
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager, paramHints]);
|
||||
|
||||
const autoCompleteConfig = autocompletion({
|
||||
override: [overRideFunction],
|
||||
|
|
@ -424,6 +439,9 @@ const EditorInput = ({
|
|||
ref={previewRef}
|
||||
>
|
||||
<CodeMirror
|
||||
onCreateEditor={(view) => {
|
||||
setCodeMirrorView(view);
|
||||
}}
|
||||
value={currentValue}
|
||||
placeholder={placeholder}
|
||||
height={isInsideQueryPane ? '100%' : showLineNumbers ? '400px' : '100%'}
|
||||
|
|
@ -460,11 +478,16 @@ const EditorInput = ({
|
|||
theme={theme}
|
||||
indentWithTab={false}
|
||||
readOnly={disabled}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Backspace') {
|
||||
startCompletion(codeMirrorView);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</CodeHinter.Portal>
|
||||
</div>
|
||||
</ErrorBoundary >
|
||||
</CodeHinter.Portal >
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ export const getAutocompletion = (input, fieldType, hints, totalReferences = 1,
|
|||
originalQueryInput,
|
||||
searchInput
|
||||
);
|
||||
return orderSuggestions(suggestions, fieldType);
|
||||
|
||||
return suggestions;
|
||||
};
|
||||
|
||||
function orderSuggestions(suggestions, validationType) {
|
||||
|
|
@ -90,10 +91,18 @@ export const generateHints = (hints, totalReferences = 1, input, searchText) =>
|
|||
const hasDepth = currentWord.includes('.');
|
||||
const lastDepth = getLastSubstring(currentWord);
|
||||
|
||||
const displayLabel = getLastDepth(displayedHint);
|
||||
let displayLabel = getLastDepth(displayedHint);
|
||||
|
||||
if (type != 'js_method') {
|
||||
const currentWordDepth = currentWord.split('.').length;
|
||||
displayLabel = hint
|
||||
.split('.')
|
||||
.slice(currentWordDepth - 1)
|
||||
.join('.');
|
||||
}
|
||||
|
||||
return {
|
||||
displayLabel: lastDepth === '' ? displayedHint : displayLabel,
|
||||
displayLabel,
|
||||
label: displayedHint,
|
||||
info: displayedHint,
|
||||
type: type === 'js_method' ? 'js_methods' : type?.toLowerCase(),
|
||||
|
|
@ -154,40 +163,24 @@ export const generateHints = (hints, totalReferences = 1, input, searchText) =>
|
|||
};
|
||||
|
||||
function filterHintsByDepth(input, hints) {
|
||||
if (input === '') return hints;
|
||||
const inputParts = input.split('.');
|
||||
const inputDepth = inputParts.length + 1;
|
||||
|
||||
const inputDepth = input.includes('.') ? input.split('.').length : 0;
|
||||
|
||||
const filteredHints = hints.filter((cm) => {
|
||||
const hintParts = cm.hint.split('.');
|
||||
|
||||
let shouldInclude =
|
||||
(cm.hint.startsWith(input) && hintParts.length === inputDepth + 1) ||
|
||||
(cm.hint.startsWith(input) && hintParts.length === inputDepth);
|
||||
|
||||
const shouldFuzzyMatch = !shouldInclude ? hintParts.length > inputDepth : false;
|
||||
|
||||
if (shouldFuzzyMatch) {
|
||||
// fuzzy match
|
||||
let matchedDepth = -1;
|
||||
for (let i = 0; i < hintParts.length; i++) {
|
||||
if (hintParts[i].includes(input)) {
|
||||
matchedDepth = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedDepth !== -1) {
|
||||
shouldInclude = hintParts.length === matchedDepth + 1;
|
||||
}
|
||||
} else if (input.endsWith('.')) {
|
||||
shouldInclude = cm.hint.startsWith(input) && hintParts.length === inputDepth;
|
||||
}
|
||||
|
||||
return shouldInclude;
|
||||
const hintsWithDepth = hints.map((hint) => {
|
||||
const hintParts = hint.hint.split('.');
|
||||
return {
|
||||
...hint,
|
||||
depth: hintParts.length,
|
||||
};
|
||||
});
|
||||
|
||||
return filteredHints;
|
||||
const filteredHints = hintsWithDepth.filter((hint) => {
|
||||
return hint.depth <= inputDepth;
|
||||
});
|
||||
|
||||
const sortedHints = filteredHints.sort((hint1, hint2) => hint1.depth - hint2.depth);
|
||||
|
||||
return sortedHints;
|
||||
}
|
||||
|
||||
export function findNearestSubstring(inputStr, currentCurosorPos) {
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ export const createResolvedSlice = (set, get) => ({
|
|||
'setVariables'
|
||||
);
|
||||
get().updateDependencyValues(`variables.${key}`);
|
||||
get().checkAndSetTrueBuildSuggestionsFlag();
|
||||
},
|
||||
|
||||
getVariable: (key, moduleId = 'canvas') => {
|
||||
|
|
@ -165,6 +166,7 @@ export const createResolvedSlice = (set, get) => ({
|
|||
'setPageVariable'
|
||||
);
|
||||
get().updateDependencyValues(`page.variables.${key}`);
|
||||
get().checkAndSetTrueBuildSuggestionsFlag();
|
||||
},
|
||||
|
||||
getPageVariable: (key, moduleId = 'canvas') => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue