Skip to content

Commit

Permalink
fix(snippets): expand variables in condition
Browse files Browse the repository at this point in the history
  • Loading branch information
chemzqm committed Mar 3, 2025
1 parent 79ad77b commit f5277a2
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 8 deletions.
21 changes: 20 additions & 1 deletion src/__tests__/snippets/parser.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable */
import * as assert from 'assert'
import { EvalKind } from '../../snippets/eval'
import { Choice, CodeBlock, ConditionString, FormatString, Marker, Placeholder, Scanner, SnippetParser, Text, TextmateSnippet, TokenType, Transform, transformEscapes, Variable } from '../../snippets/parser'
import { Choice, CodeBlock, ConditionMarker, ConditionString, FormatString, Marker, Placeholder, Scanner, SnippetParser, Text, TextmateSnippet, TokenType, Transform, transformEscapes, Variable } from '../../snippets/parser'

describe('SnippetParser', () => {

Expand Down Expand Up @@ -224,6 +224,22 @@ describe('SnippetParser', () => {
assertText('far{{id:bern {{id2:basel}}}}boo', 'far{{id:bern {{id2:basel}}}}boo')
})

test('Parser ConditionMarker', () => {
let m = new ConditionMarker(1,
[new Text('a '), new FormatString(1)],
[new Text('b '), new FormatString(2)],
)
let val = m.resolve('', ['', 'foo', 'bar'])
expect(val).toBe('b bar')
val = m.resolve('x', ['', 'foo', 'bar'])
expect(val).toBe('a foo')
m.addIfMarker(new Text('if'))
m.addElseMarker(new Text('else'))
let s = m.toTextmateString()
expect(s).toBe('(?1:a ${1}if:b ${2}else)')
expect(m.clone()).toBeDefined()
})

test('Parser, TM text', () => {
assertTextAndMarker('foo${1:bar}}', 'foobar}', Text, Placeholder, Text)
assertTextAndMarker('foo${1:bar}${2:foo}}', 'foobarfoo}', Text, Placeholder, Placeholder, Text)
Expand Down Expand Up @@ -489,8 +505,11 @@ describe('SnippetParser', () => {
expect(snip.toString()).toBe('foo f_oo')
snip = p.parse('${1:foo} ${1/^\\w//}')
expect(snip.toString()).toBe('foo oo')
snip = p.parse('${1:Foo} ${1/^(\\w+)$/\\u$1 (?1:-\\l$1)/g}')
expect(snip.toString()).toBe('Foo Foo -foo')
})


test('Parser, convert ultisnips regex', () => {
const p = new SnippetParser(true)
let snip = p.parse('${1:foo} ${1/^\\A/_/}')
Expand Down
60 changes: 53 additions & 7 deletions src/snippets/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { defaultValue } from '../util'
import { groupBy } from '../util/array'
import { CharCode } from '../util/charCode'
import { unidecode } from '../util/node'
import { getCharIndexes, toText } from '../util/string'
import { iterateCharacter, toText } from '../util/string'
import { convertRegex, evalCode, EvalKind, executePythonCode, getVariablesCode, prepareMatchCode, UltiSnippetContext } from './eval'
const logger = createLogger('snippets-parser')
const ULTISNIP_VARIABLES = ['VISUAL', 'YANK', 'UUID']
Expand Down Expand Up @@ -419,18 +419,27 @@ export class Transform extends Marker {
let ret = ''
let backslashIndexes: number[] = []
for (const marker of this._children) {
let val = ''
let len = ret.length
if (marker instanceof FormatString) {
let val = marker.resolve(groups[marker.index] || '')
val = marker.resolve(groups[marker.index] ?? '')
if (this.ultisnip && val.indexOf('\\') !== -1) {
let s = ret.length
backslashIndexes.push(...getCharIndexes(val, '\\').map(i => i + s))
for (let idx of iterateCharacter(val, '\\')) {
backslashIndexes.push(len + idx)
}
}
ret += val
} else if (marker instanceof ConditionString) {
ret += marker.resolve(groups[marker.index])
val = marker.resolve(groups[marker.index])
if (this.ultisnip) {
val = val.replace(/(?<!\\)\$(\d+)/g, (...args) => {
return groups[Number(args[1])] ?? ''
})
}
} else {
ret += marker.toString()
val = marker.toString()
}

ret += val
}
if (this.ascii) ret = unidecode(ret)
return this.ultisnip ? transformEscapes(ret, backslashIndexes) : ret
Expand Down Expand Up @@ -486,6 +495,42 @@ export class ConditionString extends Marker {
}
}

export class ConditionMarker extends Marker {
constructor(
public readonly index: number,
protected ifMarkers: Marker[] = [],
protected elseMarkers: Marker[] = []
) {
super()
}

public resolve(value: string, groups: string[]): string {
let fn = (p: string, c: Marker): string => {
return p + (c instanceof FormatString ? c.resolve(groups[c.index]) : c.toString())
}
if (value) return this.ifMarkers.reduce(fn, '')
return this.elseMarkers.reduce(fn, '')
}

public addIfMarker(marker: Marker) {
this.ifMarkers.push(marker)
}

public addElseMarker(marker: Marker) {
this.elseMarkers.push(marker)
}

public toTextmateString(): string {
let ifValue = this.ifMarkers.reduce((p, c) => p + c.toTextmateString(), '')
let elseValue = this.elseMarkers.reduce((p, c) => p + c.toTextmateString(), '')
return '(?' + this.index + ':' + ifValue + (elseValue.length > 0 ? ':' + elseValue : '') + ')'
}

public clone(): ConditionMarker {
return new ConditionMarker(this.index, this.ifMarkers.map(m => m.clone()), this.elseMarkers.map(m => m.clone()))
}
}

export class FormatString extends Marker {

constructor(
Expand Down Expand Up @@ -1572,6 +1617,7 @@ export class SnippetParser {
return false
}
let text = this._until(TokenType.CloseParen, true)
// TODO parse ConditionMarker for ultisnip
if (text) {
let i = 0
while (i < text.length) {
Expand Down
6 changes: 6 additions & 0 deletions src/util/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ export function getCharIndexes(input: string, character: string): number[] {
return res
}

export function* iterateCharacter(input: string, character: string): Iterable<number> {
for (let i = 0; i < input.length; i++) {
if (input[i] == character) yield i
}
}

export function isHighSurrogate(codePoint: number): boolean {
return codePoint >= 0xd800 && codePoint <= 0xdbff
}
Expand Down

0 comments on commit f5277a2

Please sign in to comment.