mirror of
https://github.com/tridactyl/tridactyl.git
synced 2026-09-10 07:16:33 -04:00
Swap {} for |{ }| blocks
Makes much more JS compatible without changes
This commit is contained in:
parent
00b1445575
commit
8735a6a115
|
|
@ -18,6 +18,27 @@ test.each([
|
||||||
expect([...rcFileToExCmds(rc)]).toEqual(expected)
|
expect([...rcFileToExCmds(rc)]).toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("groups multiline v2 blocks with explicit delimiters", () => {
|
||||||
|
expect([
|
||||||
|
...rcFileToExCmds("set exversion 2\nbind x |{\necho one\necho two\n}|"),
|
||||||
|
]).toEqual([
|
||||||
|
"set exversion 2",
|
||||||
|
{
|
||||||
|
source: "bind x |{\necho one\necho two\n}|",
|
||||||
|
exversion: 2,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
["bind x |{\necho one", "incomplete"],
|
||||||
|
["bind x }|", "invalid"],
|
||||||
|
])("rejects %s v2 RC blocks", (source, error) => {
|
||||||
|
expect(() => [...rcFileToExCmds(`set exversion 2\n${source}`)]).toThrow(
|
||||||
|
`${error} ex command`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test("runRc updates and saves versioned config", async () => {
|
test("runRc updates and saves versioned config", async () => {
|
||||||
await config.clear()
|
await config.clear()
|
||||||
jest.mocked(controller.acceptExCmd).mockImplementation(async cmd => {
|
jest.mocked(controller.acceptExCmd).mockImplementation(async cmd => {
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ export function getCommandlineFns(cmdline_state: {
|
||||||
if (
|
if (
|
||||||
!command.startsWith(" ") &&
|
!command.startsWith(" ") &&
|
||||||
!browser.extension.inIncognitoContext &&
|
!browser.extension.inIncognitoContext &&
|
||||||
!/(^|[\s;|{}])--?private(?=$|[\s;|{}])/.test(command)
|
!/(^|[\s;|])--?private(?=$|[\s;|])/.test(command)
|
||||||
) {
|
) {
|
||||||
State.getAsync("cmdHistory").then(c => {
|
State.getAsync("cmdHistory").then(c => {
|
||||||
cmdline_state.state.cmdHistory = c.concat([command])
|
cmdline_state.state.cmdHistory = c.concat([command])
|
||||||
|
|
|
||||||
|
|
@ -229,13 +229,15 @@ test("groups versioned programs after legacy RC commands", () => {
|
||||||
expect(rc.indexOf("bind x echo legacy | command")).toBeLessThan(
|
expect(rc.indexOf("bind x echo legacy | command")).toBeLessThan(
|
||||||
rc.indexOf("set exversion 2"),
|
rc.indexOf("set exversion 2"),
|
||||||
)
|
)
|
||||||
expect(rc).toContain("bind y {\necho one\necho two\n}")
|
expect(rc).toContain("bind y |{\necho one\necho two\n}|")
|
||||||
expect(rc).toContain("bind = {\necho equals\n}")
|
expect(rc).toContain("bind = |{\necho equals\n}|")
|
||||||
expect(rc).toContain("set exversion 1")
|
expect(rc).toContain("set exversion 1")
|
||||||
tri.config.USERCONFIG.nmaps = {
|
for (const key of [";", "\\|{", "\\}|"]) {
|
||||||
";": { source: "echo unsafe", exversion: 2 },
|
tri.config.USERCONFIG.nmaps = {
|
||||||
|
[key]: { source: "echo unsafe", exversion: 2 },
|
||||||
|
}
|
||||||
|
expect(() => tri.config.parseConfig()).toThrow("safely export")
|
||||||
}
|
}
|
||||||
expect(() => tri.config.parseConfig()).toThrow("safely export")
|
|
||||||
} finally {
|
} finally {
|
||||||
tri.config.USERCONFIG.nmaps = nmaps
|
tri.config.USERCONFIG.nmaps = nmaps
|
||||||
tri.config.USERCONFIG.exversion = exversion
|
tri.config.USERCONFIG.exversion = exversion
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,24 @@
|
||||||
*/
|
*/
|
||||||
import * as R from "ramda"
|
import * as R from "ramda"
|
||||||
import * as binding from "@src/lib/binding"
|
import * as binding from "@src/lib/binding"
|
||||||
import { ExCommand, formatExProgram, isExProgram } from "@src/lib/excmd"
|
import {
|
||||||
|
EX_BLOCK_CLOSE,
|
||||||
|
EX_BLOCK_OPEN,
|
||||||
|
ExCommand,
|
||||||
|
formatExProgram,
|
||||||
|
isExProgram,
|
||||||
|
} from "@src/lib/excmd"
|
||||||
import * as platform from "@src/lib/platform"
|
import * as platform from "@src/lib/platform"
|
||||||
import { DeepPartial } from "tsdef"
|
import { DeepPartial } from "tsdef"
|
||||||
|
|
||||||
|
const v2Syntax = [".|", "&&", "||", "|", ";", EX_BLOCK_OPEN, EX_BLOCK_CLOSE]
|
||||||
|
const escapableV2Syntax = [".|", "|", ";", EX_BLOCK_OPEN, EX_BLOCK_CLOSE]
|
||||||
const assertV2Argument = (value: string) => {
|
const assertV2Argument = (value: string) => {
|
||||||
if (/[\s'"]|^(?:\.\||&&|\|\||[|;{}])$/.test(value))
|
if (
|
||||||
|
/[\s'"]/.test(value) ||
|
||||||
|
v2Syntax.includes(value) ||
|
||||||
|
(value[0] === "\\" && escapableV2Syntax.includes(value.slice(1)))
|
||||||
|
)
|
||||||
throw new Error(`Cannot safely export dialect 2 argument: ${value}`)
|
throw new Error(`Cannot safely export dialect 2 argument: ${value}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ export async function acceptExCmd(
|
||||||
func !== stored_excmds[""].fillcmdline_tmp &&
|
func !== stored_excmds[""].fillcmdline_tmp &&
|
||||||
func !== stored_excmds[""].fillcmdline_nofocus &&
|
func !== stored_excmds[""].fillcmdline_nofocus &&
|
||||||
func !== stored_excmds[""].updatecheck &&
|
func !== stored_excmds[""].updatecheck &&
|
||||||
!/(^|[\s;|{}])-private(?=$|[\s;|{}])/.test(exstr)
|
!/(^|[\s;|])--?private(?=$|[\s;|])/.test(exstr)
|
||||||
) {
|
) {
|
||||||
lastExUpdate = State.getAsync("last_ex_str").then(
|
lastExUpdate = State.getAsync("last_ex_str").then(
|
||||||
last_ex_str => {
|
last_ex_str => {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ export interface ExProgram {
|
||||||
exversion: 2
|
exversion: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const EX_BLOCK_OPEN = "|{"
|
||||||
|
export const EX_BLOCK_CLOSE = "}|"
|
||||||
|
|
||||||
export const EX_CANCELLED = Object.freeze({
|
export const EX_CANCELLED = Object.freeze({
|
||||||
__tridactylExOutcome: "cancelled" as const,
|
__tridactylExOutcome: "cancelled" as const,
|
||||||
})
|
})
|
||||||
|
|
@ -36,7 +39,7 @@ export const formatExProgram = (command: ExCommand) => {
|
||||||
if (!isExProgram(command)) return command
|
if (!isExProgram(command)) return command
|
||||||
const prefix = command.source.startsWith("\n") ? "" : "\n"
|
const prefix = command.source.startsWith("\n") ? "" : "\n"
|
||||||
const suffix = command.source.endsWith("\n") ? "" : "\n"
|
const suffix = command.source.endsWith("\n") ? "" : "\n"
|
||||||
return `{${prefix}${command.source}${suffix}}`
|
return `${EX_BLOCK_OPEN}${prefix}${command.source}${suffix}${EX_BLOCK_CLOSE}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function joinExCommand(parts: ExCommand[]): ExCommand {
|
export function joinExCommand(parts: ExCommand[]): ExCommand {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,9 @@ test.each([
|
||||||
"echo a |b",
|
"echo a |b",
|
||||||
"echo C:\\windows\\etc",
|
"echo C:\\windows\\etc",
|
||||||
"echo /a\\|b/",
|
"echo /a\\|b/",
|
||||||
"echo {| b",
|
"echo a|{b",
|
||||||
|
"echo a}|b",
|
||||||
|
"echo a|{b}|c",
|
||||||
])("does not split protected or non-standalone operators in %s", source => {
|
])("does not split protected or non-standalone operators in %s", source => {
|
||||||
expect(shape(source)).toEqual([["text", source, undefined]])
|
expect(shape(source)).toEqual([["text", source, undefined]])
|
||||||
expect(parseStructure(source).status).toBe("complete")
|
expect(parseStructure(source).status).toBe("complete")
|
||||||
|
|
@ -46,9 +48,9 @@ test.each([
|
||||||
|
|
||||||
test("only backslash protects and is removed from standalone DSL syntax", async () => {
|
test("only backslash protects and is removed from standalone DSL syntax", async () => {
|
||||||
const run = jest.fn()
|
const run = jest.fn()
|
||||||
await evaluate("echo \\| \\&& \\|| \\.| \\; \\{ \\} \\{\\}", run)
|
await evaluate("echo \\| \\&& \\|| \\.| \\; \\|{ \\}| \\{ \\}", run)
|
||||||
expect(run).toHaveBeenCalledWith(
|
expect(run).toHaveBeenCalledWith(
|
||||||
"echo | \\&& \\|| .| ; { } {}",
|
"echo | \\&& \\|| .| ; |{ }| \\{ \\}",
|
||||||
false,
|
false,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
|
|
@ -89,12 +91,12 @@ test("protects operators in whole-line comments and separates lines", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("parses nested blocks independently", () => {
|
test("parses nested blocks independently", () => {
|
||||||
const source = "bind x { hint | tabopen } ; echo done"
|
const source = "bind x |{ hint | tabopen }| ; echo done"
|
||||||
expect(shape(source)).toEqual([
|
expect(shape(source)).toEqual([
|
||||||
["text", "bind x ", undefined],
|
["text", "bind x ", undefined],
|
||||||
[
|
[
|
||||||
"block",
|
"block",
|
||||||
"{ hint | tabopen }",
|
"|{ hint | tabopen }|",
|
||||||
[
|
[
|
||||||
["text", " hint ", undefined],
|
["text", " hint ", undefined],
|
||||||
["operator", "|", undefined],
|
["operator", "|", undefined],
|
||||||
|
|
@ -107,8 +109,14 @@ test("parses nested blocks independently", () => {
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("allows an escaped closing delimiter inside a block", async () => {
|
||||||
|
const run = jest.fn()
|
||||||
|
await evaluate("|{ echo \\}| }|", run)
|
||||||
|
expect(run).toHaveBeenCalledWith("echo }|", false, undefined, undefined)
|
||||||
|
})
|
||||||
|
|
||||||
test("reports an unterminated block as incomplete", () =>
|
test("reports an unterminated block as incomplete", () =>
|
||||||
expect(parseStructure("bind x { echo done").status).toBe("incomplete"))
|
expect(parseStructure("bind x |{ echo done").status).toBe("incomplete"))
|
||||||
|
|
||||||
test.each(["echo a |", "echo a .|", "echo a ;"])(
|
test.each(["echo a |", "echo a .|", "echo a ;"])(
|
||||||
"reports a missing right operand in %s",
|
"reports a missing right operand in %s",
|
||||||
|
|
@ -119,8 +127,8 @@ test.each([
|
||||||
"| echo a",
|
"| echo a",
|
||||||
"echo a | | echo b",
|
"echo a | | echo b",
|
||||||
"echo a ; ; echo b",
|
"echo a ; ; echo b",
|
||||||
"echo a }",
|
"echo a }|",
|
||||||
"{ echo a | }",
|
"|{ echo a | }|",
|
||||||
"js <<JS console.log(1)\necho done",
|
"js <<JS console.log(1)\necho done",
|
||||||
"js <<JS console.log(1)\rmore JS",
|
"js <<JS console.log(1)\rmore JS",
|
||||||
"js <<JS console.log(1)\r",
|
"js <<JS console.log(1)\r",
|
||||||
|
|
@ -145,7 +153,7 @@ test("evaluates pipes and sequences in order", async () => {
|
||||||
|
|
||||||
test("ignores leading colons on every command", async () => {
|
test("ignores leading colons on every command", async () => {
|
||||||
const run = jest.fn(source => source)
|
const run = jest.fn(source => source)
|
||||||
await evaluate(":one\n{ :::two } ; :echo :::argument", run)
|
await evaluate(":one\n|{ :::two }| ; :echo :::argument", run)
|
||||||
expect(run.mock.calls.map(call => call[0])).toEqual([
|
expect(run.mock.calls.map(call => call[0])).toEqual([
|
||||||
"one",
|
"one",
|
||||||
"two",
|
"two",
|
||||||
|
|
@ -357,7 +365,7 @@ test("maps a command with arguments and pipes the collected results", async () =
|
||||||
test("maps a multi-stage block", () =>
|
test("maps a multi-stage block", () =>
|
||||||
expect(
|
expect(
|
||||||
evaluate(
|
evaluate(
|
||||||
"values .| { double | stringify }",
|
"values .| |{ double | stringify }|",
|
||||||
(command, _piped, input) => {
|
(command, _piped, input) => {
|
||||||
if (command === "values") return [1, 2]
|
if (command === "values") return [1, 2]
|
||||||
if (command === "double") return input * 2
|
if (command === "double") return input * 2
|
||||||
|
|
@ -368,7 +376,7 @@ test("maps a multi-stage block", () =>
|
||||||
|
|
||||||
test("supports nested block maps", () =>
|
test("supports nested block maps", () =>
|
||||||
expect(
|
expect(
|
||||||
evaluate("matrix .| { pass .| double }", (command, _piped, input) => {
|
evaluate("matrix .| |{ pass .| double }|", (command, _piped, input) => {
|
||||||
if (command === "matrix")
|
if (command === "matrix")
|
||||||
return [
|
return [
|
||||||
[1, 2],
|
[1, 2],
|
||||||
|
|
@ -473,7 +481,7 @@ test("continues sequences after a rejected pipeline", async () => {
|
||||||
expect(run.mock.calls.map(call => call[0])).toEqual(["a", "c"])
|
expect(run.mock.calls.map(call => call[0])).toEqual(["a", "c"])
|
||||||
})
|
})
|
||||||
|
|
||||||
test.each(["a | cancel | b ; c", "a | { cancel | b } ; c"])(
|
test.each(["a | cancel | b ; c", "a | |{ cancel | b }| ; c"])(
|
||||||
"cancellation stops the whole program in %s",
|
"cancellation stops the whole program in %s",
|
||||||
async source => {
|
async source => {
|
||||||
const run = jest.fn(command =>
|
const run = jest.fn(command =>
|
||||||
|
|
@ -517,7 +525,9 @@ test("evaluates nested standalone blocks with pipeline input", async () => {
|
||||||
calls.push([source, piped, value])
|
calls.push([source, piped, value])
|
||||||
return piped ? `${source}(${value})` : source
|
return piped ? `${source}(${value})` : source
|
||||||
})
|
})
|
||||||
await expect(evaluate("a | { b | { c } }", run)).resolves.toBe("c(b(a))")
|
await expect(evaluate("a | |{ b | |{ c }| }|", run)).resolves.toBe(
|
||||||
|
"c(b(a))",
|
||||||
|
)
|
||||||
expect(calls).toEqual([
|
expect(calls).toEqual([
|
||||||
["a", false, undefined],
|
["a", false, undefined],
|
||||||
["b", true, "a"],
|
["b", true, "a"],
|
||||||
|
|
@ -527,7 +537,7 @@ test("evaluates nested standalone blocks with pipeline input", async () => {
|
||||||
|
|
||||||
test("passes a trailing block as a versioned program argument", async () => {
|
test("passes a trailing block as a versioned program argument", async () => {
|
||||||
const run = jest.fn()
|
const run = jest.fn()
|
||||||
await evaluate("bind x { a\n# keep this comment\nb }", run)
|
await evaluate("bind x |{ a\n# keep this comment\nb }|", run)
|
||||||
expect(run).toHaveBeenCalledWith("bind x", false, undefined, {
|
expect(run).toHaveBeenCalledWith("bind x", false, undefined, {
|
||||||
source: " a\n# keep this comment\nb ",
|
source: " a\n# keep this comment\nb ",
|
||||||
exversion: 2,
|
exversion: 2,
|
||||||
|
|
@ -605,17 +615,28 @@ test("formats blocks containing whole-line comments safely", async () => {
|
||||||
|
|
||||||
test("rejects unsupported nested syntax before executing", async () => {
|
test("rejects unsupported nested syntax before executing", async () => {
|
||||||
const run = jest.fn()
|
const run = jest.fn()
|
||||||
await expect(evaluate("a | { bind x { b } }", run)).rejects.toThrow(
|
await expect(evaluate("a | |{ bind x |{ b }| }|", run)).rejects.toThrow(
|
||||||
"Unsupported",
|
"Unsupported",
|
||||||
)
|
)
|
||||||
expect(run).not.toHaveBeenCalled()
|
expect(run).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
"bind { a } trailing",
|
"bind |{ a }| trailing",
|
||||||
"bind x { a } { b }",
|
"bind x |{ a }| |{ b }|",
|
||||||
"a | bind x { b }",
|
"a | bind x |{ b }|",
|
||||||
"bind x { a } <<JS\nb\nJS",
|
"bind x |{ a }| <<JS\nb\nJS",
|
||||||
])("rejects ambiguous block arguments in %s", source =>
|
])("rejects ambiguous block arguments in %s", source =>
|
||||||
expect(evaluate(source, jest.fn())).rejects.toThrow("Unsupported"),
|
expect(evaluate(source, jest.fn())).rejects.toThrow("Unsupported"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
"js if (ready) { work() }",
|
||||||
|
"js const value = { key: 1 }",
|
||||||
|
"js let url; { let node = find() }",
|
||||||
|
"echo { unmatched } braces",
|
||||||
|
])("treats ordinary braces as command text in %s", async source => {
|
||||||
|
const run = jest.fn()
|
||||||
|
await evaluate(source, run)
|
||||||
|
expect(run).toHaveBeenCalledWith(source, false, undefined, undefined)
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
import { ExProgram, isExCancelled, stripLeadingColons } from "@src/lib/excmd"
|
import {
|
||||||
|
EX_BLOCK_CLOSE,
|
||||||
|
EX_BLOCK_OPEN,
|
||||||
|
ExProgram,
|
||||||
|
isExCancelled,
|
||||||
|
stripLeadingColons,
|
||||||
|
} from "@src/lib/excmd"
|
||||||
|
|
||||||
const operators = [".|", "|", ";"] as const
|
const operators = [".|", "|", ";"] as const
|
||||||
|
|
||||||
|
|
@ -20,13 +26,20 @@ export type ExPart =
|
||||||
| ({ type: "operator"; operator: ExOperator } & Span)
|
| ({ type: "operator"; operator: ExOperator } & Span)
|
||||||
| ({ type: "comment" } & Span)
|
| ({ type: "comment" } & Span)
|
||||||
| ({ type: "heredoc"; bodyStart: number; bodyEnd: number } & Span)
|
| ({ type: "heredoc"; bodyStart: number; bodyEnd: number } & Span)
|
||||||
| ({ type: "block"; body: ExStructure } & Span)
|
| ({
|
||||||
|
type: "block"
|
||||||
|
body: ExStructure
|
||||||
|
bodyStart: number
|
||||||
|
bodyEnd: number
|
||||||
|
} & Span)
|
||||||
|
|
||||||
interface ParseResult extends ExStructure {
|
interface ParseResult extends ExStructure {
|
||||||
end: number
|
end: number
|
||||||
|
bodyEnd?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const boundaries = " \t\r\n"
|
const boundaries = " \t\r\n"
|
||||||
|
const blockDelimiters = [EX_BLOCK_OPEN, EX_BLOCK_CLOSE]
|
||||||
|
|
||||||
const status = (
|
const status = (
|
||||||
invalid: boolean,
|
invalid: boolean,
|
||||||
|
|
@ -54,13 +67,14 @@ function escapedSyntaxEnd(source: string, index: number) {
|
||||||
isBoundary(source[index + candidate.length + 1]),
|
isBoundary(source[index + candidate.length + 1]),
|
||||||
)
|
)
|
||||||
if (operator) return index + operator.length + 1
|
if (operator) return index + operator.length + 1
|
||||||
const escaped = source[index + 1]
|
const blockDelimiter = blockDelimiters.find(
|
||||||
if (
|
delimiter =>
|
||||||
(escaped === "{" || escaped === "}") &&
|
source.startsWith(delimiter, index + 1) &&
|
||||||
isBraceBoundary(source, index - 1) &&
|
isBoundary(source[index - 1]) &&
|
||||||
isBraceBoundary(source, index + 2)
|
isBoundary(source[index + delimiter.length + 1]),
|
||||||
)
|
)
|
||||||
return index + 2
|
if (blockDelimiter) return index + blockDelimiter.length + 1
|
||||||
|
const escaped = source[index + 1]
|
||||||
if (
|
if (
|
||||||
(escaped === "#" || escaped === '"') &&
|
(escaped === "#" || escaped === '"') &&
|
||||||
isWholeLineComment(source, index)
|
isWholeLineComment(source, index)
|
||||||
|
|
@ -69,18 +83,12 @@ function escapedSyntaxEnd(source: string, index: number) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function isBraceBoundary(source: string, index: number) {
|
function blockDelimiterAt(source: string, index: number) {
|
||||||
const character = source[index]
|
if (!isBoundary(source[index - 1])) return undefined
|
||||||
return (
|
return blockDelimiters.find(
|
||||||
isBoundary(character) ||
|
delimiter =>
|
||||||
"{}".includes(character) ||
|
source.startsWith(delimiter, index) &&
|
||||||
(character === "\\" && "{}".includes(source[index + 1]))
|
isBoundary(source[index + delimiter.length]),
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isStandaloneBrace(source: string, index: number) {
|
|
||||||
return (
|
|
||||||
isBraceBoundary(source, index - 1) && isBraceBoundary(source, index + 1)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,14 +214,18 @@ function parseRange(
|
||||||
textStart = index + 1
|
textStart = index + 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (character === "{" && isStandaloneBrace(source, index)) {
|
const blockDelimiter = blockDelimiterAt(source, index)
|
||||||
|
if (blockDelimiter === EX_BLOCK_OPEN) {
|
||||||
text(index)
|
text(index)
|
||||||
const child = parseRange(source, index + 1, true)
|
const bodyStart = index + EX_BLOCK_OPEN.length
|
||||||
|
const child = parseRange(source, bodyStart, true)
|
||||||
const end = child.end
|
const end = child.end
|
||||||
parts.push({
|
parts.push({
|
||||||
type: "block",
|
type: "block",
|
||||||
start: index,
|
start: index,
|
||||||
end,
|
end,
|
||||||
|
bodyStart,
|
||||||
|
bodyEnd: child.bodyEnd ?? source.length,
|
||||||
body: { parts: child.parts, status: child.status },
|
body: { parts: child.parts, status: child.status },
|
||||||
})
|
})
|
||||||
incomplete = incomplete || child.status === "incomplete"
|
incomplete = incomplete || child.status === "incomplete"
|
||||||
|
|
@ -223,7 +235,7 @@ function parseRange(
|
||||||
textStart = end
|
textStart = end
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (nested && character === "}" && isStandaloneBrace(source, index)) {
|
if (nested && blockDelimiter === EX_BLOCK_CLOSE) {
|
||||||
text(index)
|
text(index)
|
||||||
return {
|
return {
|
||||||
parts,
|
parts,
|
||||||
|
|
@ -231,11 +243,13 @@ function parseRange(
|
||||||
invalid || expectation === "operand",
|
invalid || expectation === "operand",
|
||||||
incomplete,
|
incomplete,
|
||||||
),
|
),
|
||||||
end: index + 1,
|
end: index + EX_BLOCK_CLOSE.length,
|
||||||
|
bodyEnd: index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!nested && character === "}" && isStandaloneBrace(source, index)) {
|
if (!nested && blockDelimiter === EX_BLOCK_CLOSE) {
|
||||||
invalid = true
|
invalid = true
|
||||||
|
index += EX_BLOCK_CLOSE.length - 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -340,7 +354,7 @@ function compile(
|
||||||
command: blockCommand,
|
command: blockCommand,
|
||||||
piped,
|
piped,
|
||||||
program: {
|
program: {
|
||||||
source: source.slice(block.start + 1, block.end - 1),
|
source: source.slice(block.bodyStart, block.bodyEnd),
|
||||||
exversion: 2,
|
exversion: 2,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -57,12 +57,12 @@ echo hello
|
||||||
# bare expressions are allowed in pipes
|
# bare expressions are allowed in pipes
|
||||||
:js [{hello: "world"}, {mellow: "yellow"}, {hello: "universe"}] .| _.hello | filter | fillcmdline
|
:js [{hello: "world"}, {mellow: "yellow"}, {hello: "universe"}] .| _.hello | filter | fillcmdline
|
||||||
|
|
||||||
# { } form blocks
|
# |{ }| form blocks
|
||||||
:bind ;Y {
|
:bind ;Y |{
|
||||||
hint -eJc img |
|
hint -eJc img |
|
||||||
_.src |
|
_.src |
|
||||||
yankimage
|
yankimage
|
||||||
}
|
}|
|
||||||
|
|
||||||
# heredocs are supported for making javascript more pleasant
|
# heredocs are supported for making javascript more pleasant
|
||||||
:js <<JS
|
:js <<JS
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue