Console/fix folding with script (#216817)

Fixes https://github.com/elastic/kibana/issues/212902

## Summary

This PR fixes the logic for finding folding ranges by ignoring
opening/closing markers inside triple-quote strings.

**How to test:**

Verify that the `script` field in the following request is folded
correctly:

```
POST _ingest/pipeline/_simulate
{
  "pipeline": {
    "processors": [
      {
        "script": {
          "source":
          """
            for (field in params['fields']){
                if (!$(field, '').isEmpty()){
                    def value = $(field, '');
                    def hash = value.sha256();

                    // Now we need to traverse as deep as needed
                    // and write to that field
                    // because we do not have a simple
                    // set operation available
                    
                "SCRIPT":  parts = field.splitOnToken('.');
                    

                }
            }
          """,
          "params": {
            "fields": [
              "user.name",
              "geo.city",
              "does.not.exist",
              "this.is.quite.a.deep.field"
            ]
          }
        }
      }
    ]
  }
}
```

Note: The logic is for finding the ranges is best-effort without
compromising performance. We currently iterate through each line in the
text and use regex, but there are some cases which are not covered by
this logic; for example, opening parenthesis, followed by a string in
the same line would not be foldable. In order to cover all cases
correctly, we would need to iterate through every single character, but
that would make the logic much more complex and might affect performance
if we have a lot of text in the editor, as these folding ranges are
computed on every change in the editor.
This commit is contained in:
Elena Stoeva 2025-04-15 14:45:23 +01:00 committed by GitHub
parent f402033eab
commit d9477a74d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 32 additions and 17 deletions

View file

@ -14,25 +14,29 @@ describe('getFoldingRanges', () => {
// 1 PUT /test/_doc/1
// 2 {
// 3 "some_key": {
// 4 "some_inner_key": """{
// 5 "multi_line": "json string"
// 6 }""",
// 7 "some_other_key": 123
// 8 },
// 9 "outer_key_2": [
// 10 1,
// 11 2,
// 12 3
// 13 ]
// 14 }
// 4 "some_inner_key": """
// 5 "multi_line_json": { // Should ignore this parenthesis
// 6 "foo": "bar"
// 7 } // Should ignore this parenthesis
// 8 """,
// 9 "some_other_key": 123
// 10 },
// 11 "outer_key_2": [
// 12 1,
// 13 2,
// 14 3
// 15 ]
// 16 }
const SAMPLE_INPUT = [
'PUT /test/_doc/1',
'{ ',
' "some_key": { ',
' "some_inner_key": """{',
' "multi_line": "json string"',
' }""",',
' "some_inner_key": """',
' "multi_line_json": {',
' "foo": "bar',
' }',
' """,',
' "some_other_key": 123',
' },',
' "outer_key_2": [',
@ -45,14 +49,14 @@ describe('getFoldingRanges', () => {
it('returns correct ranges for parentheses', () => {
const expectedRanges = [
{ start: 3, end: 7 },
{ start: 2, end: 13 },
{ start: 3, end: 9 },
{ start: 2, end: 15 },
];
expect(getFoldingRanges(SAMPLE_INPUT, '{', '}')).toEqual(expectedRanges);
});
it('returns correct ranges for square brackets', () => {
const expectedRanges = [{ start: 9, end: 12 }];
const expectedRanges = [{ start: 11, end: 14 }];
expect(getFoldingRanges(SAMPLE_INPUT, '[', ']')).toEqual(expectedRanges);
});
});

View file

@ -23,14 +23,25 @@ const getClosingLineRegex = (closingMarker: string) => {
return new RegExp(regExStr);
};
// A regex that matches a line containing a complete triple-quote string
const inlineTripleQuoteString = /^.*?"""(.*?)""".*$/m;
export const getFoldingRanges = (lines: string[], openingMarker: string, closingMarker: string) => {
const ranges: monaco.languages.ProviderResult<monaco.languages.FoldingRange[]> = [];
const stack: number[] = [];
const openingLineRegex = getOpeningLineRegex(openingMarker);
const closingLineRegex = getClosingLineRegex(closingMarker);
let insideTripleQuotes = false;
for (let i = 0; i < lines.length; i++) {
const lineContent = lines[i].trim();
if (lineContent.includes('"""') && !inlineTripleQuoteString.test(lineContent)) {
insideTripleQuotes = !insideTripleQuotes;
}
if (insideTripleQuotes) {
// If we are inside a multi-line triple-quote string, ignore opening/closing markers
continue;
}
if (openingLineRegex.test(lineContent)) {
stack.push(i + 1); // Line numbers start from 1 so we need to add 1 to the current index
} else if (closingLineRegex.test(lineContent)) {