fix: Autocomplete object value binding (#15999)
## Description Fixes #15950 Fixes #16141 ## Type of change - Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? **Test plan** - [ ] https://github.com/appsmithorg/TestSmith/issues/1982 - [ ] https://github.com/appsmithorg/TestSmith/issues/2049 ## Checklist: - [x] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes
This commit is contained in:
parent
e8c1fb1c4a
commit
2ec11a4dee
|
|
@ -18,6 +18,7 @@ describe("Tern server", () => {
|
|||
doc: ({
|
||||
getCursor: () => ({ ch: 0, line: 0 }),
|
||||
getLine: () => "{{Api.}}",
|
||||
getValue: () => "{{Api.}}",
|
||||
} as unknown) as CodeMirror.Doc,
|
||||
changed: null,
|
||||
},
|
||||
|
|
@ -29,6 +30,7 @@ describe("Tern server", () => {
|
|||
doc: ({
|
||||
getCursor: () => ({ ch: 0, line: 0 }),
|
||||
getLine: () => "a{{Api.}}",
|
||||
getValue: () => "a{{Api.}}",
|
||||
} as unknown) as CodeMirror.Doc,
|
||||
changed: null,
|
||||
},
|
||||
|
|
@ -38,17 +40,30 @@ describe("Tern server", () => {
|
|||
input: {
|
||||
name: "test",
|
||||
doc: ({
|
||||
getCursor: () => ({ ch: 2, line: 0 }),
|
||||
getLine: () => "a{{Api.}}",
|
||||
getCursor: () => ({ ch: 10, line: 0 }),
|
||||
getLine: () => "a{{Api.}}bc",
|
||||
getValue: () => "a{{Api.}}bc",
|
||||
} as unknown) as CodeMirror.Doc,
|
||||
changed: null,
|
||||
},
|
||||
expectedOutput: "{{Api.}}",
|
||||
expectedOutput: "a{{Api.}}bc",
|
||||
},
|
||||
{
|
||||
input: {
|
||||
name: "test",
|
||||
doc: ({
|
||||
getCursor: () => ({ ch: 4, line: 0 }),
|
||||
getLine: () => "a{{Api.}}",
|
||||
getValue: () => "a{{Api.}}",
|
||||
} as unknown) as CodeMirror.Doc,
|
||||
changed: null,
|
||||
},
|
||||
expectedOutput: "Api.",
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
const value = TernServer.getFocusedDynamicValue(testCase.input);
|
||||
const { value } = TernServer.getFocusedDocValueAndPos(testCase.input);
|
||||
expect(value).toBe(testCase.expectedOutput);
|
||||
});
|
||||
});
|
||||
|
|
@ -62,7 +77,7 @@ describe("Tern server", () => {
|
|||
getCursor: () => ({ ch: 0, line: 0 }),
|
||||
getLine: () => "{{Api.}}",
|
||||
somethingSelected: () => false,
|
||||
getValue: () => "",
|
||||
getValue: () => "{{Api.}}",
|
||||
} as unknown) as CodeMirror.Doc,
|
||||
changed: null,
|
||||
},
|
||||
|
|
@ -75,7 +90,7 @@ describe("Tern server", () => {
|
|||
getCursor: () => ({ ch: 0, line: 0 }),
|
||||
getLine: () => "{{Api.}}",
|
||||
somethingSelected: () => false,
|
||||
getValue: () => "",
|
||||
getValue: () => "{{Api.}}",
|
||||
} as unknown) as CodeMirror.Doc,
|
||||
changed: null,
|
||||
},
|
||||
|
|
@ -85,20 +100,32 @@ describe("Tern server", () => {
|
|||
input: {
|
||||
name: "test",
|
||||
doc: ({
|
||||
getCursor: () => ({ ch: 3, line: 0 }),
|
||||
getCursor: () => ({ ch: 8, line: 0 }),
|
||||
getLine: () => "g {{Api.}}",
|
||||
somethingSelected: () => false,
|
||||
getValue: () => "",
|
||||
getValue: () => "g {{Api.}}",
|
||||
} as unknown) as CodeMirror.Doc,
|
||||
changed: null,
|
||||
},
|
||||
expectedOutput: { ch: 3, line: 0 },
|
||||
expectedOutput: { ch: 4, line: 0 },
|
||||
},
|
||||
{
|
||||
input: {
|
||||
name: "test",
|
||||
doc: ({
|
||||
getCursor: () => ({ ch: 7, line: 1 }),
|
||||
getLine: () => "c{{Api.}}",
|
||||
somethingSelected: () => false,
|
||||
getValue: () => "ab\nc{{Api.}}",
|
||||
} as unknown) as CodeMirror.Doc,
|
||||
changed: null,
|
||||
},
|
||||
expectedOutput: { ch: 4, line: 0 },
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
const request = TernServer.buildRequest(testCase.input, {});
|
||||
|
||||
expect(request.query.end).toEqual(testCase.expectedOutput);
|
||||
});
|
||||
});
|
||||
|
|
@ -115,6 +142,7 @@ describe("Tern server", () => {
|
|||
getCursor: () => ({ ch: 2, line: 0 }),
|
||||
getLine: () => "{{}}",
|
||||
somethingSelected: () => false,
|
||||
getValue: () => "{{}}",
|
||||
} as unknown) as CodeMirror.Doc,
|
||||
},
|
||||
requestCallbackData: {
|
||||
|
|
@ -134,12 +162,13 @@ describe("Tern server", () => {
|
|||
getCursor: () => ({ ch: 3, line: 0 }),
|
||||
getLine: () => " {{}}",
|
||||
somethingSelected: () => false,
|
||||
getValue: () => " {{}}",
|
||||
} as unknown) as CodeMirror.Doc,
|
||||
},
|
||||
requestCallbackData: {
|
||||
completions: [{ name: "Api1" }],
|
||||
start: { ch: 2, line: 0 },
|
||||
end: { ch: 6, line: 0 },
|
||||
start: { ch: 0, line: 0 },
|
||||
end: { ch: 4, line: 0 },
|
||||
},
|
||||
},
|
||||
expectedOutput: { ch: 3, line: 0 },
|
||||
|
|
|
|||
|
|
@ -207,20 +207,20 @@ class TernServer {
|
|||
}
|
||||
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);
|
||||
const { extraChars } = this.getFocusedDocValueAndPos(doc);
|
||||
|
||||
let completions: Completion[] = [];
|
||||
let after = "";
|
||||
const { end, start } = data;
|
||||
|
||||
const from = {
|
||||
...start,
|
||||
ch: start.ch + index,
|
||||
ch: start.ch + extraChars,
|
||||
line: cursor.line,
|
||||
};
|
||||
const to = {
|
||||
...end,
|
||||
ch: end.ch + index,
|
||||
ch: end.ch + extraChars,
|
||||
line: cursor.line,
|
||||
};
|
||||
if (
|
||||
|
|
@ -392,7 +392,7 @@ class TernServer {
|
|||
|
||||
addDoc(name: string, doc: CodeMirror.Doc) {
|
||||
const data = { doc: doc, name: name, changed: null };
|
||||
this.server.addFile(name, this.getFocusedDynamicValue(data));
|
||||
this.server.addFile(name, this.getFocusedDocValueAndPos(data).value);
|
||||
CodeMirror.on(doc, "change", this.trackChange.bind(this));
|
||||
return (this.docs[name] = data);
|
||||
}
|
||||
|
|
@ -412,7 +412,13 @@ class TernServer {
|
|||
query.depth = 0;
|
||||
query.sort = true;
|
||||
if (query.end == null) {
|
||||
query.end = pos || doc.doc.getCursor("end");
|
||||
const positions = pos || doc.doc.getCursor("end");
|
||||
const { end } = this.getFocusedDocValueAndPos(doc);
|
||||
query.end = {
|
||||
...positions,
|
||||
...end,
|
||||
};
|
||||
|
||||
if (doc.doc.somethingSelected()) query.start = doc.doc.getCursor("start");
|
||||
}
|
||||
const startPos = query.start || query.end;
|
||||
|
|
@ -435,7 +441,7 @@ class TernServer {
|
|||
files.push({
|
||||
type: "full",
|
||||
name: doc.name,
|
||||
text: this.docValue(doc),
|
||||
text: this.getFocusedDocValueAndPos(doc).value,
|
||||
});
|
||||
query.file = doc.name;
|
||||
doc.changed = null;
|
||||
|
|
@ -448,7 +454,7 @@ class TernServer {
|
|||
files.push({
|
||||
type: "full",
|
||||
name: doc.name,
|
||||
text: this.docValue(doc),
|
||||
text: this.getFocusedDocValueAndPos(doc).value,
|
||||
});
|
||||
}
|
||||
for (const name in this.docs) {
|
||||
|
|
@ -457,7 +463,7 @@ class TernServer {
|
|||
files.push({
|
||||
type: "full",
|
||||
name: cur.name,
|
||||
text: this.docValue(cur),
|
||||
text: this.getFocusedDocValueAndPos(doc).value,
|
||||
});
|
||||
cur.changed = null;
|
||||
}
|
||||
|
|
@ -508,7 +514,7 @@ class TernServer {
|
|||
{
|
||||
type: "full",
|
||||
name: doc.name,
|
||||
text: this.getFocusedDynamicValue(doc),
|
||||
text: this.docValue(doc),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -529,23 +535,106 @@ class TernServer {
|
|||
return doc.doc.getValue();
|
||||
}
|
||||
|
||||
getFocusedDynamicValue(doc: TernDoc) {
|
||||
const cursor = doc.doc.getCursor();
|
||||
const value = this.lineValue(doc);
|
||||
const stringSegments = getDynamicStringSegments(value);
|
||||
const dynamicStrings = stringSegments.filter((segment) => {
|
||||
if (isDynamicValue(segment)) {
|
||||
const index = value.indexOf(segment);
|
||||
getFocusedDocValueAndPos(
|
||||
doc: TernDoc,
|
||||
): { value: string; end: { line: number; ch: number }; extraChars: number } {
|
||||
const cursor = doc.doc.getCursor("end");
|
||||
const value = this.docValue(doc);
|
||||
const lineValue = this.lineValue(doc);
|
||||
let extraChars = 0;
|
||||
|
||||
if (cursor.ch >= index && cursor.ch <= index + segment.length) {
|
||||
return true;
|
||||
const stringSegments = getDynamicStringSegments(value);
|
||||
if (stringSegments.length === 1) {
|
||||
return {
|
||||
value,
|
||||
end: {
|
||||
line: cursor.line,
|
||||
ch: cursor.ch,
|
||||
},
|
||||
extraChars,
|
||||
};
|
||||
}
|
||||
|
||||
let dynamicString = value;
|
||||
|
||||
let newCursorLine = cursor.line;
|
||||
let newCursorPosition = cursor.ch;
|
||||
|
||||
let currentLine = 0;
|
||||
|
||||
for (let index = 0; index < stringSegments.length; index++) {
|
||||
// segment is divided according to binding {{}}
|
||||
|
||||
const segment = stringSegments[index];
|
||||
let currentSegment = segment;
|
||||
if (segment.startsWith("{{")) {
|
||||
currentSegment = segment.replace("{{", "");
|
||||
if (currentSegment.endsWith("}}")) {
|
||||
currentSegment = currentSegment.slice(0, currentSegment.length - 2);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
// subSegment is segment further divided by EOD char (\n)
|
||||
const subSegments = currentSegment.split("\n");
|
||||
const countEODCharInSegment = subSegments.length - 1;
|
||||
const segmentEndLine = countEODCharInSegment + currentLine;
|
||||
|
||||
return dynamicStrings.length ? dynamicStrings[0] : value;
|
||||
/**
|
||||
* 3 case for cursor to point inside segment
|
||||
* 1. cursor is before the {{ :-
|
||||
* 2. cursor is inside segment :-
|
||||
* - if cursor is after {{ on same line
|
||||
* - if cursor is after {{ in different line
|
||||
* - if cursor is before }} on same line
|
||||
* 3. cursor is after the }} :-
|
||||
*
|
||||
*/
|
||||
|
||||
const isCursorInBetweenSegmentStartAndEndLine =
|
||||
cursor.line > currentLine && cursor.line < segmentEndLine;
|
||||
|
||||
const isCursorAtSegmentStartLine = cursor.line === currentLine;
|
||||
const isCursorAfterBindingOpenAtSegmentStart =
|
||||
isCursorAtSegmentStartLine && cursor.ch > lineValue.indexOf("{{") + 1;
|
||||
const isCursorAtSegmentEndLine = cursor.line === segmentEndLine;
|
||||
const isCursorBeforeBindingCloseAtSegmentEnd =
|
||||
isCursorAtSegmentEndLine && cursor.ch < lineValue.indexOf("}}") + 1;
|
||||
|
||||
const isSegmentStartLineAndEndLineSame = currentLine === segmentEndLine;
|
||||
const isCursorBetweenSingleLineSegmentBinding =
|
||||
isSegmentStartLineAndEndLineSame &&
|
||||
isCursorBeforeBindingCloseAtSegmentEnd &&
|
||||
isCursorAfterBindingOpenAtSegmentStart;
|
||||
|
||||
const isCursorPointingInsideSegment =
|
||||
isCursorInBetweenSegmentStartAndEndLine ||
|
||||
(isSegmentStartLineAndEndLineSame &&
|
||||
isCursorBetweenSingleLineSegmentBinding);
|
||||
(!isSegmentStartLineAndEndLineSame &&
|
||||
isCursorBeforeBindingCloseAtSegmentEnd) ||
|
||||
isCursorAfterBindingOpenAtSegmentStart;
|
||||
|
||||
if (isDynamicValue(segment) && isCursorPointingInsideSegment) {
|
||||
dynamicString = currentSegment;
|
||||
newCursorLine = cursor.line - currentLine;
|
||||
if (lineValue.includes("{{")) {
|
||||
extraChars = lineValue.indexOf("{{") + 2;
|
||||
}
|
||||
newCursorPosition = cursor.ch - extraChars;
|
||||
|
||||
break;
|
||||
}
|
||||
currentLine = segmentEndLine;
|
||||
}
|
||||
|
||||
return {
|
||||
value: dynamicString,
|
||||
end: {
|
||||
line: newCursorLine,
|
||||
ch: newCursorPosition,
|
||||
},
|
||||
extraChars,
|
||||
};
|
||||
}
|
||||
|
||||
getFragmentAround(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user