PromucFlow_constructor/app/client/src/utils/autocomplete/TernServer.ts

867 lines
24 KiB
TypeScript
Raw Normal View History

/* eslint-disable @typescript-eslint/ban-ts-comment */
2020-05-20 11:30:53 +00:00
// Heavily inspired from https://github.com/codemirror/CodeMirror/blob/master/addon/tern/tern.js
2020-06-18 14:16:49 +00:00
import tern, { Server, Def } from "tern";
2021-07-20 10:02:56 +00:00
import ecma from "constants/defs/ecmascript.json";
2020-06-12 10:56:46 +00:00
import lodash from "constants/defs/lodash.json";
import base64 from "constants/defs/base64-js.json";
2020-09-08 08:43:46 +00:00
import moment from "constants/defs/moment.json";
import xmlJs from "constants/defs/xmlParser.json";
import forge from "constants/defs/forge.json";
2020-05-20 11:30:53 +00:00
import CodeMirror, { Hint, Pos, cmpPos } from "codemirror";
import {
2021-07-20 10:02:56 +00:00
getDynamicBindings,
getDynamicStringSegments,
isDynamicValue,
} from "utils/DynamicBindingUtils";
import {
GLOBAL_DEFS,
GLOBAL_FUNCTIONS,
} from "utils/autocomplete/EntityDefinitions";
import { FieldEntityInformation } from "components/editorComponents/CodeEditor/EditorConfig";
2021-07-20 10:02:56 +00:00
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
import SortRules from "./dataTypeSortRules";
import _ from "lodash";
const DEFS: Def[] = [
// @ts-ignore
ecma,
GLOBAL_FUNCTIONS,
GLOBAL_DEFS,
lodash,
base64,
moment,
xmlJs,
forge,
];
2020-05-20 11:30:53 +00:00
const bigDoc = 250;
const cls = "CodeMirror-Tern-";
const hintDelay = 1700;
[Feature] Unified New Nav (#5558) * temp commit * using onsubmit to continue using action on form * added recaptcha site key to env example file * moved the recaptcha lib loading logic to signup page * removed unnecessary edit * handle the case where the recaptcha token is not provided as env var * added proper env var config for client * recaptcha config for ansible * recaptcha config for heroku * recaptcha config for k8s * updated app.json * fixed the typos * added more description for env vars * removed api key * minor typo fix * added new integration button * updated the add int default link * added active and create new tabs * added the empty components to tabs. will control the section manually. * added proper grid for integrations page * added vertical tabs * Added secondary tabs to integrations page * added separate page for new apis * classname changes * added new components for active queries, new queries etc. * added a separate component for data source list * adding screen component conditionally, to be showing upon user's choice * 1. Added grid styling to datasource home 2. Added connect buttons to em * fixed data source security banner * updated the styling for new api page * added tertiary menu for active integrations * updated styling for active connections * updated collapse component to work properly * added show more option to active data sources * Slash commands feature init commit * Added more commands * Introduced JSX to render custom commands * Merge conflict fix * Spacing changes * removed apis/db tabs and replaced em with integrations tab * removed the unnecessary + integrations btn * Added slash commands button * Adjust styles for better ui * Ordered the action entries under integrations * Added new datasource command * updated the getURL with proper params * updated the link of create datasource btn * updated the back btn link from data source editor * Show connect data cta in property pane * Styling fixes * Fix margin * added scrollable content to create new * added on click scroll to create new page * fixed a bug, creating new datasource twice * added new action creator for integrations. * Minor changes to add new bindings command. Changed ui behaviour of / button * UI style change * updated the query editor to match the over all theme * updated the query editor tabs * Added the run btn to empty response screens * minor fix * updated the bg color of api type drop down * updated the url being visited after delete api/query * removed log * Insert binding command UI change * More UI changes * removed unnecessary junk from integrations editor index * clean up, removed unnecessary files * removed useless routes * for debugger only checking if integrations editor * Removed all the links for api/query home pages * Move command actions to a saga Added support to binding the data back to the widget when are new API is created from widget * Added reverse binding for DB queries * Show / button only on hover * not routing to integrations on create query/api * Hide actions from suggestions in action pages * removed the query/datasource/api home pages * Changes widget.data to widget in slash commands * Show dependencies in property pane * Fix warning * fixed scrolling issue * will show a list of queries and apis for action picker * showing icons for each action under integrations * Fix dropdown not showing up * Minor refactoring. Changed commands * added a way to list data sources in action creators * Update query page url * cam show icons for datasources * Removed unused code * Feature/slash commands (#5002) * Slash commands feature init commit * Added more commands * Introduced JSX to render custom commands * Merge conflict fix * Spacing changes * Added slash commands button * Adjust styles for better ui * Added new datasource command * Minor changes to add new bindings command. Changed ui behaviour of / button * UI style change * Insert binding command UI change * More UI changes * Move command actions to a saga Added support to binding the data back to the widget when are new API is created from widget * Added reverse binding for DB queries * Show / button only on hover * Hide actions from suggestions in action pages * Changes widget.data to widget in slash commands * Minor refactoring. Changed commands * Removed unused code * remove more unusued code * Added support to generate new api from a datasource in quick commands * Code correction to use types * Refactored commands code * Minor bug fixes * Remove new integrations command for actions. Fixed autocomplete not showing up * Changes to prevent autocomplete trigger for navigation commands * Prevent hinter execution when show hint is open already. * Show hinter on focus * Update text to be called in the omnibar * updated the copy for empty active datasources * Update url * Fix text decoration * updated the redirection for back btns * Use themes * Add cypress test * fixed back btn nav * fetching form configs for datasources * a callback fixed * Fix slash command not executed on click (#5540) * Replace the value if not a string else append * Log commands menu events * updated mock data base navigation * updated mock data base navigation * updated the close editors and back buttons * All back btns from editors will go back to data sources and back from data source will go back to canvas * fixed bg colors * minor styled updates * removed margin from header of generic datasource * warnings fixes * If user is already on the location not redirecting em * when editing, will check if the coming from data source and redirect accordingly * updated redirection for newly created api/queries * updated back btn for newly created datasources * back for new curl goes to data sources * Revert "[Fix] revert new nav (#5533)" This reverts commit 1647815d * remaining original reverted chagnes * fixed the width of incoming/outgoing entity bar in property pane * removing residue from resolved merge conflicts * Fix widget icons not visible in dropdown menu * minor fix to use proper integration URL * updated the URLs for unified datasources * converted back and close to btns from banners * on accessing data source from sidebar, it'll always go to view mode * updated the edit path for saas editors * Added saved state for google sheet * on google sheet delete redirecting to create new * minor fix * fixed the redirection call on saving a datasource * removed save and test cmd as it wasn't needed * Removing test cases to be fixed by Arun * commenting more tests to be fixed by Arun * updated call api cy command * Fix extra margin issue * fixed the update datasource saga * fixed video spec * Revert "commenting more tests to be fixed by Arun" This reverts commit 42087a95ad77107401a1619e4c2d4c541a81d6c3. * Revert "Removing test cases to be fixed by Arun" This reverts commit f6fad67e558d22045114a90409428ef9b737478f. * fixed the entity explorer query datasource spec * cautious fix * update widget locators * fixed leave org test * fixes for FormWidgets * updated the image spec * Use memo * Fix debugger url checks * for copy and delete widget pointing directly to svgs * Fix entity text * Fix styling and show tooltip for property pane dependencies * removed the unnecessary callback * added a separate saga to to redirect to new integrations using onSuccess * Bug Fixes - New nav (#5629) * will show scrollbar only on hover * made mock data cards clickable * fixed the grid view * fixed the cursor position when clicking on / btn * updated the hint for `/` command * binding prompt will close on focus change * hiding / command for api body * hiding / command for query pane * Added 2 new icons * Fix cursor position on selecting a binding and clicking on the slash menu button * trying out fix to copyWidget cy command * removing zero width space characters from the property pane text Co-authored-by: arunvjn <arun@appsmith.com> Co-authored-by: Akash N <akash@codemonk.in> Co-authored-by: arunvjn <32433245+arunvjn@users.noreply.github.com> Co-authored-by: Rishabh Saxena <rishabh.robben@gmail.com>
2021-07-07 03:46:16 +00:00
export type Completion = Hint & {
2020-05-20 11:30:53 +00:00
origin: string;
type: AutocompleteDataType;
2020-05-20 11:30:53 +00:00
data: {
doc: string;
};
render?: any;
isHeader?: boolean;
2020-05-20 11:30:53 +00:00
};
[Feature] Unified New Nav (#5558) * temp commit * using onsubmit to continue using action on form * added recaptcha site key to env example file * moved the recaptcha lib loading logic to signup page * removed unnecessary edit * handle the case where the recaptcha token is not provided as env var * added proper env var config for client * recaptcha config for ansible * recaptcha config for heroku * recaptcha config for k8s * updated app.json * fixed the typos * added more description for env vars * removed api key * minor typo fix * added new integration button * updated the add int default link * added active and create new tabs * added the empty components to tabs. will control the section manually. * added proper grid for integrations page * added vertical tabs * Added secondary tabs to integrations page * added separate page for new apis * classname changes * added new components for active queries, new queries etc. * added a separate component for data source list * adding screen component conditionally, to be showing upon user's choice * 1. Added grid styling to datasource home 2. Added connect buttons to em * fixed data source security banner * updated the styling for new api page * added tertiary menu for active integrations * updated styling for active connections * updated collapse component to work properly * added show more option to active data sources * Slash commands feature init commit * Added more commands * Introduced JSX to render custom commands * Merge conflict fix * Spacing changes * removed apis/db tabs and replaced em with integrations tab * removed the unnecessary + integrations btn * Added slash commands button * Adjust styles for better ui * Ordered the action entries under integrations * Added new datasource command * updated the getURL with proper params * updated the link of create datasource btn * updated the back btn link from data source editor * Show connect data cta in property pane * Styling fixes * Fix margin * added scrollable content to create new * added on click scroll to create new page * fixed a bug, creating new datasource twice * added new action creator for integrations. * Minor changes to add new bindings command. Changed ui behaviour of / button * UI style change * updated the query editor to match the over all theme * updated the query editor tabs * Added the run btn to empty response screens * minor fix * updated the bg color of api type drop down * updated the url being visited after delete api/query * removed log * Insert binding command UI change * More UI changes * removed unnecessary junk from integrations editor index * clean up, removed unnecessary files * removed useless routes * for debugger only checking if integrations editor * Removed all the links for api/query home pages * Move command actions to a saga Added support to binding the data back to the widget when are new API is created from widget * Added reverse binding for DB queries * Show / button only on hover * not routing to integrations on create query/api * Hide actions from suggestions in action pages * removed the query/datasource/api home pages * Changes widget.data to widget in slash commands * Show dependencies in property pane * Fix warning * fixed scrolling issue * will show a list of queries and apis for action picker * showing icons for each action under integrations * Fix dropdown not showing up * Minor refactoring. Changed commands * added a way to list data sources in action creators * Update query page url * cam show icons for datasources * Removed unused code * Feature/slash commands (#5002) * Slash commands feature init commit * Added more commands * Introduced JSX to render custom commands * Merge conflict fix * Spacing changes * Added slash commands button * Adjust styles for better ui * Added new datasource command * Minor changes to add new bindings command. Changed ui behaviour of / button * UI style change * Insert binding command UI change * More UI changes * Move command actions to a saga Added support to binding the data back to the widget when are new API is created from widget * Added reverse binding for DB queries * Show / button only on hover * Hide actions from suggestions in action pages * Changes widget.data to widget in slash commands * Minor refactoring. Changed commands * Removed unused code * remove more unusued code * Added support to generate new api from a datasource in quick commands * Code correction to use types * Refactored commands code * Minor bug fixes * Remove new integrations command for actions. Fixed autocomplete not showing up * Changes to prevent autocomplete trigger for navigation commands * Prevent hinter execution when show hint is open already. * Show hinter on focus * Update text to be called in the omnibar * updated the copy for empty active datasources * Update url * Fix text decoration * updated the redirection for back btns * Use themes * Add cypress test * fixed back btn nav * fetching form configs for datasources * a callback fixed * Fix slash command not executed on click (#5540) * Replace the value if not a string else append * Log commands menu events * updated mock data base navigation * updated mock data base navigation * updated the close editors and back buttons * All back btns from editors will go back to data sources and back from data source will go back to canvas * fixed bg colors * minor styled updates * removed margin from header of generic datasource * warnings fixes * If user is already on the location not redirecting em * when editing, will check if the coming from data source and redirect accordingly * updated redirection for newly created api/queries * updated back btn for newly created datasources * back for new curl goes to data sources * Revert "[Fix] revert new nav (#5533)" This reverts commit 1647815d * remaining original reverted chagnes * fixed the width of incoming/outgoing entity bar in property pane * removing residue from resolved merge conflicts * Fix widget icons not visible in dropdown menu * minor fix to use proper integration URL * updated the URLs for unified datasources * converted back and close to btns from banners * on accessing data source from sidebar, it'll always go to view mode * updated the edit path for saas editors * Added saved state for google sheet * on google sheet delete redirecting to create new * minor fix * fixed the redirection call on saving a datasource * removed save and test cmd as it wasn't needed * Removing test cases to be fixed by Arun * commenting more tests to be fixed by Arun * updated call api cy command * Fix extra margin issue * fixed the update datasource saga * fixed video spec * Revert "commenting more tests to be fixed by Arun" This reverts commit 42087a95ad77107401a1619e4c2d4c541a81d6c3. * Revert "Removing test cases to be fixed by Arun" This reverts commit f6fad67e558d22045114a90409428ef9b737478f. * fixed the entity explorer query datasource spec * cautious fix * update widget locators * fixed leave org test * fixes for FormWidgets * updated the image spec * Use memo * Fix debugger url checks * for copy and delete widget pointing directly to svgs * Fix entity text * Fix styling and show tooltip for property pane dependencies * removed the unnecessary callback * added a separate saga to to redirect to new integrations using onSuccess * Bug Fixes - New nav (#5629) * will show scrollbar only on hover * made mock data cards clickable * fixed the grid view * fixed the cursor position when clicking on / btn * updated the hint for `/` command * binding prompt will close on focus change * hiding / command for api body * hiding / command for query pane * Added 2 new icons * Fix cursor position on selecting a binding and clicking on the slash menu button * trying out fix to copyWidget cy command * removing zero width space characters from the property pane text Co-authored-by: arunvjn <arun@appsmith.com> Co-authored-by: Akash N <akash@codemonk.in> Co-authored-by: arunvjn <32433245+arunvjn@users.noreply.github.com> Co-authored-by: Rishabh Saxena <rishabh.robben@gmail.com>
2021-07-07 03:46:16 +00:00
export type CommandsCompletion = Completion & {
action?: () => void;
shortcut: string;
triggerCompletionsPostPick?: boolean;
[Feature] Unified New Nav (#5558) * temp commit * using onsubmit to continue using action on form * added recaptcha site key to env example file * moved the recaptcha lib loading logic to signup page * removed unnecessary edit * handle the case where the recaptcha token is not provided as env var * added proper env var config for client * recaptcha config for ansible * recaptcha config for heroku * recaptcha config for k8s * updated app.json * fixed the typos * added more description for env vars * removed api key * minor typo fix * added new integration button * updated the add int default link * added active and create new tabs * added the empty components to tabs. will control the section manually. * added proper grid for integrations page * added vertical tabs * Added secondary tabs to integrations page * added separate page for new apis * classname changes * added new components for active queries, new queries etc. * added a separate component for data source list * adding screen component conditionally, to be showing upon user's choice * 1. Added grid styling to datasource home 2. Added connect buttons to em * fixed data source security banner * updated the styling for new api page * added tertiary menu for active integrations * updated styling for active connections * updated collapse component to work properly * added show more option to active data sources * Slash commands feature init commit * Added more commands * Introduced JSX to render custom commands * Merge conflict fix * Spacing changes * removed apis/db tabs and replaced em with integrations tab * removed the unnecessary + integrations btn * Added slash commands button * Adjust styles for better ui * Ordered the action entries under integrations * Added new datasource command * updated the getURL with proper params * updated the link of create datasource btn * updated the back btn link from data source editor * Show connect data cta in property pane * Styling fixes * Fix margin * added scrollable content to create new * added on click scroll to create new page * fixed a bug, creating new datasource twice * added new action creator for integrations. * Minor changes to add new bindings command. Changed ui behaviour of / button * UI style change * updated the query editor to match the over all theme * updated the query editor tabs * Added the run btn to empty response screens * minor fix * updated the bg color of api type drop down * updated the url being visited after delete api/query * removed log * Insert binding command UI change * More UI changes * removed unnecessary junk from integrations editor index * clean up, removed unnecessary files * removed useless routes * for debugger only checking if integrations editor * Removed all the links for api/query home pages * Move command actions to a saga Added support to binding the data back to the widget when are new API is created from widget * Added reverse binding for DB queries * Show / button only on hover * not routing to integrations on create query/api * Hide actions from suggestions in action pages * removed the query/datasource/api home pages * Changes widget.data to widget in slash commands * Show dependencies in property pane * Fix warning * fixed scrolling issue * will show a list of queries and apis for action picker * showing icons for each action under integrations * Fix dropdown not showing up * Minor refactoring. Changed commands * added a way to list data sources in action creators * Update query page url * cam show icons for datasources * Removed unused code * Feature/slash commands (#5002) * Slash commands feature init commit * Added more commands * Introduced JSX to render custom commands * Merge conflict fix * Spacing changes * Added slash commands button * Adjust styles for better ui * Added new datasource command * Minor changes to add new bindings command. Changed ui behaviour of / button * UI style change * Insert binding command UI change * More UI changes * Move command actions to a saga Added support to binding the data back to the widget when are new API is created from widget * Added reverse binding for DB queries * Show / button only on hover * Hide actions from suggestions in action pages * Changes widget.data to widget in slash commands * Minor refactoring. Changed commands * Removed unused code * remove more unusued code * Added support to generate new api from a datasource in quick commands * Code correction to use types * Refactored commands code * Minor bug fixes * Remove new integrations command for actions. Fixed autocomplete not showing up * Changes to prevent autocomplete trigger for navigation commands * Prevent hinter execution when show hint is open already. * Show hinter on focus * Update text to be called in the omnibar * updated the copy for empty active datasources * Update url * Fix text decoration * updated the redirection for back btns * Use themes * Add cypress test * fixed back btn nav * fetching form configs for datasources * a callback fixed * Fix slash command not executed on click (#5540) * Replace the value if not a string else append * Log commands menu events * updated mock data base navigation * updated mock data base navigation * updated the close editors and back buttons * All back btns from editors will go back to data sources and back from data source will go back to canvas * fixed bg colors * minor styled updates * removed margin from header of generic datasource * warnings fixes * If user is already on the location not redirecting em * when editing, will check if the coming from data source and redirect accordingly * updated redirection for newly created api/queries * updated back btn for newly created datasources * back for new curl goes to data sources * Revert "[Fix] revert new nav (#5533)" This reverts commit 1647815d * remaining original reverted chagnes * fixed the width of incoming/outgoing entity bar in property pane * removing residue from resolved merge conflicts * Fix widget icons not visible in dropdown menu * minor fix to use proper integration URL * updated the URLs for unified datasources * converted back and close to btns from banners * on accessing data source from sidebar, it'll always go to view mode * updated the edit path for saas editors * Added saved state for google sheet * on google sheet delete redirecting to create new * minor fix * fixed the redirection call on saving a datasource * removed save and test cmd as it wasn't needed * Removing test cases to be fixed by Arun * commenting more tests to be fixed by Arun * updated call api cy command * Fix extra margin issue * fixed the update datasource saga * fixed video spec * Revert "commenting more tests to be fixed by Arun" This reverts commit 42087a95ad77107401a1619e4c2d4c541a81d6c3. * Revert "Removing test cases to be fixed by Arun" This reverts commit f6fad67e558d22045114a90409428ef9b737478f. * fixed the entity explorer query datasource spec * cautious fix * update widget locators * fixed leave org test * fixes for FormWidgets * updated the image spec * Use memo * Fix debugger url checks * for copy and delete widget pointing directly to svgs * Fix entity text * Fix styling and show tooltip for property pane dependencies * removed the unnecessary callback * added a separate saga to to redirect to new integrations using onSuccess * Bug Fixes - New nav (#5629) * will show scrollbar only on hover * made mock data cards clickable * fixed the grid view * fixed the cursor position when clicking on / btn * updated the hint for `/` command * binding prompt will close on focus change * hiding / command for api body * hiding / command for query pane * Added 2 new icons * Fix cursor position on selecting a binding and clicking on the slash menu button * trying out fix to copyWidget cy command * removing zero width space characters from the property pane text Co-authored-by: arunvjn <arun@appsmith.com> Co-authored-by: Akash N <akash@codemonk.in> Co-authored-by: arunvjn <32433245+arunvjn@users.noreply.github.com> Co-authored-by: Rishabh Saxena <rishabh.robben@gmail.com>
2021-07-07 03:46:16 +00:00
};
2020-05-20 11:30:53 +00:00
type TernDocs = Record<string, TernDoc>;
type TernDoc = {
doc: CodeMirror.Doc;
name: string;
changed: { to: number; from: number } | null;
};
export enum AutocompleteDataType {
OBJECT = "OBJECT",
NUMBER = "NUMBER",
ARRAY = "ARRAY",
FUNCTION = "FUNCTION",
BOOLEAN = "BOOLEAN",
STRING = "STRING",
UNKNOWN = "UNKNOWN",
}
2020-05-20 11:30:53 +00:00
type ArgHints = {
start: CodeMirror.Position;
type: { args: any[]; rettype: null | string };
name: string;
guess: boolean;
doc: CodeMirror.Doc;
};
export type DataTreeDefEntityInformation = {
type: ENTITY_TYPE;
subType: string;
};
2020-05-20 11:30:53 +00:00
class TernServer {
server: Server;
docs: TernDocs = Object.create(null);
cachedArgHints: ArgHints | null = null;
active: any;
fieldEntityInformation: FieldEntityInformation = {};
defEntityInformation: Map<string, DataTreeDefEntityInformation> = new Map<
string,
DataTreeDefEntityInformation
>();
2020-05-20 11:30:53 +00:00
constructor() {
2020-05-20 11:30:53 +00:00
this.server = new tern.Server({
async: true,
defs: DEFS,
2020-05-20 11:30:53 +00:00
});
}
2021-07-20 10:02:56 +00:00
resetServer() {
this.server = new tern.Server({
async: true,
defs: DEFS,
});
this.docs = Object.create(null);
}
complete(cm: CodeMirror.Editor) {
cm.showHint({
hint: this.getHint.bind(this),
completeSingle: false,
extraKeys: {
Up: (cm: CodeMirror.Editor, handle: any) => {
handle.moveFocus(-1);
if (this.active.isHeader === true) {
handle.moveFocus(-1);
}
},
Down: (cm: CodeMirror.Editor, handle: any) => {
handle.moveFocus(1);
if (this.active.isHeader === true) {
handle.moveFocus(1);
}
},
},
});
2020-05-20 11:30:53 +00:00
}
showType(cm: CodeMirror.Editor) {
this.showContextInfo(cm, "type");
}
showDocs(cm: CodeMirror.Editor) {
this.showContextInfo(cm, "documentation", (data: any) => {
if (data.url) {
window.open(data.url, "_blank");
}
});
}
updateDef(
name: string,
def: Def,
entityInfo?: Map<string, DataTreeDefEntityInformation>,
) {
2020-06-18 14:16:49 +00:00
this.server.deleteDefs(name);
// @ts-ignore: No types available
this.server.addDefs(def);
if (entityInfo) this.defEntityInformation = entityInfo;
2020-06-18 14:16:49 +00:00
}
removeDef(name: string) {
this.server.deleteDefs(name);
}
requestCallback(error: any, data: any, cm: CodeMirror.Editor, resolve: any) {
if (error) return this.showError(cm, error);
if (data.completions.length === 0) {
return this.showError(cm, "No suggestions");
}
const doc = this.findDoc(cm.getDoc());
const cursor = cm.getCursor();
const lineValue = this.lineValue(doc);
const focusedValue = this.getFocusedDynamicValue(doc);
const index = lineValue.indexOf(focusedValue);
let completions: Completion[] = [];
let after = "";
const { end, start } = data;
const from = {
...start,
ch: start.ch + index,
line: cursor.line,
};
const to = {
...end,
ch: end.ch + index,
line: cursor.line,
};
if (
cm.getRange(Pos(from.line, from.ch - 2), from) === '["' &&
cm.getRange(to, Pos(to.line, to.ch + 2)) !== '"]'
) {
after = '"]';
}
2021-07-20 10:02:56 +00:00
const bindings = getDynamicBindings(cm.getValue());
const onlySingleBinding = bindings.stringSegments.length === 1;
const searchText = (bindings.jsSnippets[0] || "").trim();
for (let i = 0; i < data.completions.length; ++i) {
const completion = data.completions[i];
let className = this.typeToIcon(completion.type, completion.isKeyword);
const dataType = this.getDataType(completion.type);
if (data.guess) className += " " + cls + "guess";
2021-07-20 10:02:56 +00:00
let completionText = completion.name + after;
if (dataType === "FUNCTION") {
completionText = completionText + "()";
2021-06-04 07:33:54 +00:00
}
const codeMirrorCompletion: Completion = {
2021-07-20 10:02:56 +00:00
text: completionText,
displayText: completionText,
className: className,
data: completion,
origin: completion.origin,
type: dataType,
isHeader: false,
};
if (completion.isKeyword) {
codeMirrorCompletion.render = (
element: HTMLElement,
self: any,
data: any,
) => {
element.setAttribute("keyword", data.displayText);
element.innerHTML = data.displayText;
};
}
completions.push(codeMirrorCompletion);
}
2021-07-20 10:02:56 +00:00
completions = this.sortAndFilterCompletions(
2021-07-20 10:02:56 +00:00
completions,
onlySingleBinding,
searchText,
);
const indexToBeSelected =
completions.length && completions[0].isHeader ? 1 : 0;
const obj = {
from: from,
to: to,
list: completions,
selectedHint: indexToBeSelected,
};
let tooltip: HTMLElement | undefined = undefined;
CodeMirror.on(obj, "close", () => this.remove(tooltip));
CodeMirror.on(obj, "update", () => this.remove(tooltip));
CodeMirror.on(
obj,
"select",
(cur: { data: { doc: string } }, node: any) => {
this.active = cur;
this.remove(tooltip);
const content = cur.data.doc;
if (content) {
tooltip = this.makeTooltip(
node.parentNode.getBoundingClientRect().right + window.pageXOffset,
node.getBoundingClientRect().top + window.pageYOffset,
content,
);
tooltip.className += " " + cls + "hint-doc";
CodeMirror.on(
cm,
"keyup",
(cm: CodeMirror.Editor, keyboardEvent: KeyboardEvent) => {
if (
keyboardEvent.code === "Space" &&
keyboardEvent.ctrlKey &&
tooltip
) {
tooltip.className += " visible";
}
},
);
}
},
);
resolve(obj);
return obj;
}
2020-05-20 11:30:53 +00:00
getHint(cm: CodeMirror.Editor) {
2020-12-24 04:32:25 +00:00
return new Promise((resolve) => {
2020-05-20 11:30:53 +00:00
this.request(
cm,
{
type: "completions",
types: true,
docs: true,
urls: true,
origins: true,
caseInsensitive: true,
guess: false,
inLiteral: false,
2020-05-20 11:30:53 +00:00
},
(error, data) => this.requestCallback(error, data, cm, resolve),
2020-05-20 11:30:53 +00:00
);
});
}
sortAndFilterCompletions(
2021-07-20 10:02:56 +00:00
completions: Completion[],
findBestMatch: boolean,
bestMatchSearch: string,
) {
const {
entityName,
entityType,
expectedType = AutocompleteDataType.UNKNOWN,
} = this.fieldEntityInformation;
2021-07-20 10:02:56 +00:00
type CompletionType =
| "DATA_TREE"
| "MATCHING_TYPE"
| "OTHER"
| "CONTEXT"
| "JS"
| "LIBRARY";
const completionType: Record<CompletionType, Completion[]> = {
MATCHING_TYPE: [],
DATA_TREE: [],
CONTEXT: [],
JS: [],
LIBRARY: [],
OTHER: [],
};
completions.forEach((completion) => {
if (entityName && completion.text.includes(entityName)) {
return;
}
if (completion.origin) {
if (completion.origin && completion.origin.startsWith("DATA_TREE")) {
if (completion.text.includes(".")) {
// nested paths (with ".") should only be used for best match
if (completion.type === expectedType) {
2021-07-20 10:02:56 +00:00
completionType.MATCHING_TYPE.push(completion);
}
} else if (completion.origin === "DATA_TREE.APPSMITH.FUNCTIONS") {
2021-07-20 10:02:56 +00:00
// Global functions should be in best match as well as DataTree
if (
!entityType ||
ENTITY_TYPE.ACTION === entityType ||
ENTITY_TYPE.JSACTION === entityType ||
ENTITY_TYPE.WIDGET === entityType
) {
completionType.MATCHING_TYPE.push(completion);
completionType.DATA_TREE.push(completion);
}
2021-07-20 10:02:56 +00:00
} else {
// All top level entities are set in data tree
completionType.DATA_TREE.push(completion);
}
return;
}
if (
completion.origin === "[doc]" ||
completion.origin === "customDataTree"
) {
// [doc] are variables defined in the current context
// customDataTree are implicit context defined by platform
completionType.CONTEXT.push(completion);
return;
}
if (
completion.origin === "ecmascript" ||
completion.origin === "base64-js"
) {
completionType.JS.push(completion);
return;
}
if (completion.origin.startsWith("LIB/")) {
completionType.LIBRARY.push(completion);
return;
}
}
// Generally keywords or other unCategorised completions
completionType.OTHER.push(completion);
});
completionType.DATA_TREE = completionType.DATA_TREE.sort(
(a: Completion, b: Completion) => {
if (a.type === "FUNCTION" && b.type !== "FUNCTION") {
return 1;
} else if (a.type !== "FUNCTION" && b.type === "FUNCTION") {
return -1;
}
2020-05-20 11:30:53 +00:00
return a.text.toLowerCase().localeCompare(b.text.toLowerCase());
2021-07-20 10:02:56 +00:00
},
);
2021-07-20 10:02:56 +00:00
completionType.MATCHING_TYPE = completionType.MATCHING_TYPE.filter((c) =>
c.text.toLowerCase().startsWith(bestMatchSearch.toLowerCase()),
);
2021-07-20 10:02:56 +00:00
if (findBestMatch && completionType.MATCHING_TYPE.length) {
const sortedMatches: Completion[] = [];
const groupedMatches = _.groupBy(completionType.MATCHING_TYPE, (c) => {
const name = c.text.split(".")[0];
const entityInfo = this.defEntityInformation.get(name);
if (!entityInfo) return c.text;
return c.text.replace(name, entityInfo.subType);
2021-07-20 10:02:56 +00:00
});
const expectedRules = SortRules[expectedType];
for (const [key, value] of Object.entries(groupedMatches)) {
const name = key.split(".")[0];
if (name === "JSACTION") {
sortedMatches.push(...value);
} else if (expectedRules.indexOf(key) !== -1) {
sortedMatches.push(...value);
2021-07-20 10:02:56 +00:00
}
}
2021-07-20 10:02:56 +00:00
sortedMatches.sort((a, b) => {
let aRank = 0;
let bRank = 0;
const aName = a.text.split(".")[0];
const bName = b.text.split(".")[0];
const aEntityInfo = this.defEntityInformation.get(aName);
const bEntityInfo = this.defEntityInformation.get(bName);
if (!aEntityInfo) return -1;
if (!bEntityInfo) return 1;
if (aEntityInfo.type === entityType) {
2021-07-20 10:02:56 +00:00
aRank = aRank + 1;
}
if (bEntityInfo.type === entityType) {
2021-07-20 10:02:56 +00:00
bRank = bRank + 1;
}
return aRank - bRank;
});
completionType.MATCHING_TYPE = _.take(sortedMatches, 3);
if (completionType.MATCHING_TYPE.length) {
completionType.MATCHING_TYPE.unshift(
createCompletionHeader("Best Match"),
);
completionType.DATA_TREE.unshift(
createCompletionHeader("Search Results"),
);
}
} else {
// Clear any matching type because we dont want to find best match
completionType.MATCHING_TYPE = [];
}
return [
2021-07-20 10:02:56 +00:00
...completionType.CONTEXT,
...completionType.MATCHING_TYPE,
...completionType.DATA_TREE,
...completionType.LIBRARY,
...completionType.JS,
...completionType.OTHER,
];
2020-05-20 11:30:53 +00:00
}
getDataType(type: string): AutocompleteDataType {
if (type === "?") return AutocompleteDataType.UNKNOWN;
else if (type === "number") return AutocompleteDataType.NUMBER;
else if (type === "string") return AutocompleteDataType.STRING;
else if (type === "bool") return AutocompleteDataType.BOOLEAN;
else if (type === "array") return AutocompleteDataType.ARRAY;
else if (/^fn\(/.test(type)) return AutocompleteDataType.FUNCTION;
else if (/^\[/.test(type)) return AutocompleteDataType.ARRAY;
else return AutocompleteDataType.OBJECT;
}
typeToIcon(type: string, isKeyword: boolean) {
2020-05-20 11:30:53 +00:00
let suffix;
if (isKeyword) suffix = "keyword";
else if (type === "?") suffix = "unknown";
2020-05-20 11:30:53 +00:00
else if (type === "number" || type === "string" || type === "bool")
suffix = type;
else if (/^fn\(/.test(type)) suffix = "fn";
else if (/^\[/.test(type)) suffix = "array";
else suffix = "object";
return cls + "completion " + cls + "completion-" + suffix;
}
showContextInfo(cm: CodeMirror.Editor, queryName: string, callbackFn?: any) {
2020-05-20 11:30:53 +00:00
this.request(cm, { type: queryName }, (error, data) => {
if (error) return this.showError(cm, error);
const tip = this.elt(
"span",
null,
this.elt("strong", null, data.type || "not found"),
);
if (data.doc) tip.appendChild(document.createTextNode(" — " + data.doc));
if (data.url) {
tip.appendChild(document.createTextNode(" "));
const child = tip.appendChild(this.elt("a", null, "[docs]"));
// @ts-ignore: No types available
2020-05-20 11:30:53 +00:00
child.href = data.url;
// @ts-ignore: No types available
2020-05-20 11:30:53 +00:00
child.target = "_blank";
}
this.tempTooltip(cm, tip);
if (callbackFn) callbackFn(data);
});
}
request(
cm: CodeMirror.Editor,
query: {
type: string;
types?: boolean;
docs?: boolean;
urls?: boolean;
origins?: boolean;
caseInsensitive?: boolean;
2020-05-20 11:30:53 +00:00
preferFunction?: boolean;
end?: CodeMirror.Position;
guess?: boolean;
inLiteral?: boolean;
2020-05-20 11:30:53 +00:00
},
callbackFn: (error: any, data: any) => void,
pos?: CodeMirror.Position,
) {
const doc = this.findDoc(cm.getDoc());
const request = this.buildRequest(doc, query, pos);
// @ts-ignore: No types available
2020-05-20 11:30:53 +00:00
this.server.request(request, callbackFn);
}
findDoc(doc: CodeMirror.Doc, name?: string): TernDoc {
for (const n in this.docs) {
const cur = this.docs[n];
if (cur.doc === doc) return cur;
}
if (!name) {
let n;
for (let i = 0; ; ++i) {
n = "[doc" + (i || "") + "]";
if (!this.docs[n]) {
name = n;
break;
}
}
}
return this.addDoc(name, doc);
}
addDoc(name: string, doc: CodeMirror.Doc) {
const data = { doc: doc, name: name, changed: null };
this.server.addFile(name, this.getFocusedDynamicValue(data));
2020-05-20 11:30:53 +00:00
CodeMirror.on(doc, "change", this.trackChange.bind(this));
return (this.docs[name] = data);
}
buildRequest(
doc: TernDoc,
query: {
type?: string;
types?: boolean;
docs?: boolean;
urls?: boolean;
origins?: boolean;
fullDocs?: any;
lineCharPositions?: any;
end?: any;
start?: any;
file?: any;
2021-07-20 10:02:56 +00:00
includeKeywords?: boolean;
inLiteral?: boolean;
2020-05-20 11:30:53 +00:00
},
pos?: CodeMirror.Position,
) {
const files = [];
let offsetLines = 0;
const allowFragments = !query.fullDocs;
if (!allowFragments) delete query.fullDocs;
query.lineCharPositions = true;
2021-07-20 10:02:56 +00:00
query.includeKeywords = true;
2020-05-20 11:30:53 +00:00
if (!query.end) {
const lineValue = this.lineValue(doc);
const focusedValue = this.getFocusedDynamicValue(doc);
const index = lineValue.indexOf(focusedValue);
const positions = pos || doc.doc.getCursor("end");
const queryChPosition = positions.ch - index;
query.end = {
...positions,
line: 0,
ch: queryChPosition,
};
2020-05-20 11:30:53 +00:00
if (doc.doc.somethingSelected()) {
query.start = doc.doc.getCursor("start");
}
}
const startPos = query.start || query.end;
if (doc.changed) {
if (
doc.doc.lineCount() > bigDoc &&
allowFragments &&
doc.changed.to - doc.changed.from < 100 &&
doc.changed.from <= startPos.line &&
doc.changed.to > query.end.line
) {
files.push(this.getFragmentAround(doc, startPos, query.end));
query.file = "#0";
offsetLines = files[0].offsetLines;
if (query.start) {
query.start = Pos(query.start.line - -offsetLines, query.start.ch);
}
query.end = Pos(query.end.line - offsetLines, query.end.ch);
} else {
files.push({
type: "full",
name: doc.name,
text: this.getFocusedDynamicValue(doc),
2020-05-20 11:30:53 +00:00
});
query.file = doc.name;
doc.changed = null;
}
} else {
query.file = doc.name;
}
for (const name in this.docs) {
const cur = this.docs[name];
if (cur.changed && cur !== doc) {
files.push({
type: "full",
name: cur.name,
text: this.getFocusedDynamicValue(cur),
2020-05-20 11:30:53 +00:00
});
cur.changed = null;
}
}
2020-05-20 11:30:53 +00:00
return { query: query, files: files };
}
trackChange(
doc: CodeMirror.Doc,
change: {
to: CodeMirror.Position;
from: CodeMirror.Position;
text: string | any[];
},
) {
const data = this.findDoc(doc);
const argHints = this.cachedArgHints;
if (
argHints &&
argHints.doc === doc &&
cmpPos(argHints.start, change.to) >= 0
)
this.cachedArgHints = null;
let changed = data.changed;
if (changed === null)
data.changed = changed = { from: change.from.line, to: change.from.line };
const end = change.from.line + (change.text.length - 1);
if (change.from.line < changed.to)
changed.to = changed.to - (change.to.line - end);
if (end >= changed.to) changed.to = end + 1;
if (changed.from > change.from.line) changed.from = change.from.line;
if (doc.lineCount() > bigDoc && changed.to - changed.from > 100)
setTimeout(() => {
if (data.changed && data.changed.to - data.changed.from > 100)
this.sendDoc(data);
}, 200);
}
sendDoc(doc: TernDoc) {
this.server.request(
{
// @ts-ignore: No types available
files: [
// @ts-ignore: No types available
{
type: "full",
name: doc.name,
text: this.getFocusedDynamicValue(doc),
},
],
},
2020-05-20 11:30:53 +00:00
function(error: Error) {
if (error) window.console.error(error);
else doc.changed = null;
},
);
}
lineValue(doc: TernDoc) {
const cursor = doc.doc.getCursor();
return doc.doc.getLine(cursor.line);
}
2020-05-20 11:30:53 +00:00
docValue(doc: TernDoc) {
return doc.doc.getValue();
}
getFocusedDynamicValue(doc: TernDoc) {
const cursor = doc.doc.getCursor();
const value = this.lineValue(doc);
const stringSegments = getDynamicStringSegments(value);
2020-12-24 04:32:25 +00:00
const dynamicStrings = stringSegments.filter((segment) => {
if (isDynamicValue(segment)) {
const index = value.indexOf(segment);
if (cursor.ch >= index && cursor.ch <= index + segment.length) {
return true;
}
}
return false;
});
return dynamicStrings.length ? dynamicStrings[0] : value;
}
2020-05-20 11:30:53 +00:00
getFragmentAround(
data: TernDoc,
start: CodeMirror.Position,
end: CodeMirror.Position,
) {
const doc = data.doc;
let minIndent = null;
let minLine = null;
let endLine;
const tabSize = 4;
for (let p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) {
const line = doc.getLine(p),
fn = line.search(/\bfunction\b/);
if (fn < 0) continue;
const indent = CodeMirror.countColumn(line, null, tabSize);
if (minIndent != null && minIndent <= indent) continue;
minIndent = indent;
minLine = p;
}
if (minLine === null) minLine = Math.max(0, start.line - 1);
const max = Math.min(doc.lastLine(), end.line + 20);
if (
minIndent === null ||
minIndent ===
CodeMirror.countColumn(doc.getLine(start.line), null, tabSize)
)
endLine = max;
else
for (endLine = end.line + 1; endLine < max; ++endLine) {
const indent = CodeMirror.countColumn(
doc.getLine(endLine),
null,
tabSize,
);
if (indent <= minIndent) break;
}
const from = Pos(minLine, 0);
return {
type: "part",
name: data.name,
offsetLines: from.line,
text: doc.getRange(
from,
Pos(endLine, end.line === endLine ? undefined : 0),
),
};
}
showError(cm: CodeMirror.Editor, msg: string) {
this.tempTooltip(cm, String(msg));
}
tempTooltip(cm: CodeMirror.Editor, content: HTMLElement | string) {
if (cm.state.ternTooltip) this.remove(cm.state.ternTooltip);
if (cm.state.completionActive) {
// @ts-ignore: No types available
2020-05-20 11:30:53 +00:00
cm.closeHint();
}
const where = cm.cursorCoords();
const tip = (cm.state.ternTooltip = this.makeTooltip(
// @ts-ignore: No types available
2020-05-20 11:30:53 +00:00
where.right + 1,
where.bottom,
content,
));
const maybeClear = () => {
old = true;
if (!mouseOnTip) clear();
};
const clear = () => {
cm.state.ternTooltip = null;
if (tip.parentNode) this.fadeOut(tip);
clearActivity();
};
let mouseOnTip = false;
let old = false;
CodeMirror.on(tip, "mousemove", function() {
mouseOnTip = true;
});
CodeMirror.on(tip, "mouseout", function(e: MouseEvent) {
const related = e.relatedTarget;
// @ts-ignore: No types available
2020-05-20 11:30:53 +00:00
if (!related || !CodeMirror.contains(tip, related)) {
if (old) clear();
else mouseOnTip = false;
}
});
setTimeout(maybeClear, hintDelay);
const clearActivity = this.onEditorActivity(cm, clear);
}
onEditorActivity(
cm: CodeMirror.Editor,
f: (instance: CodeMirror.Editor) => void,
) {
cm.on("cursorActivity", f);
cm.on("blur", f);
cm.on("scroll", f);
cm.on("setDoc", f);
return function() {
cm.off("cursorActivity", f);
cm.off("blur", f);
cm.off("scroll", f);
cm.off("setDoc", f);
};
}
makeTooltip(x: number, y: number, content: HTMLElement | string) {
const node = this.elt("div", cls + "tooltip", content);
node.style.left = x + "px";
node.style.top = y + "px";
document.body.appendChild(node);
return node;
}
remove(node?: HTMLElement) {
if (node) {
const p = node.parentNode;
if (p) p.removeChild(node);
}
}
elt(
tagName: string,
cls: string | null,
content: string | HTMLElement,
): HTMLElement {
const e = document.createElement(tagName);
if (cls) e.className = cls;
if (content) {
const eltNode =
typeof content === "string"
? document.createTextNode(content)
: content;
e.appendChild(eltNode);
}
return e;
}
fadeOut(tooltip: HTMLElement) {
2020-06-04 13:49:22 +00:00
this.remove(tooltip);
2020-05-20 11:30:53 +00:00
}
2021-07-20 10:02:56 +00:00
setEntityInformation(entityInformation: FieldEntityInformation) {
this.fieldEntityInformation = entityInformation;
2021-07-20 10:02:56 +00:00
}
2020-05-20 11:30:53 +00:00
}
2021-07-20 10:02:56 +00:00
export const createCompletionHeader = (name: string): Completion => ({
text: name,
displayText: name,
className: "CodeMirror-hint-header",
data: { doc: "" },
origin: "",
type: AutocompleteDataType.UNKNOWN,
2021-07-20 10:02:56 +00:00
isHeader: true,
});
export default new TernServer();