Skip to content

Commit

Permalink
feat: add cy.runLuaCode() for executing lua in the neovim instance (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
mikavilpas authored Nov 24, 2024
1 parent ccc4ea3 commit 04ac8eb
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ jobs:
node-version-file: .nvmrc
cache: "pnpm"
- run: pnpm install
- run: pnpm build
- uses: reviewdog/action-eslint@v1.32.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review # Change reporter.
- run: pnpm build
51 changes: 51 additions & 0 deletions packages/integration-tests/cypress/e2e/neovim.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,55 @@ describe("neovim features", () => {
})
})
})

it("can run lua code and get its result", () => {
cy.visit("/")
cy.startNeovim().then(() => {
// wait until text on the start screen is visible
cy.contains("If you see this text, Neovim is ready!")

// can do math
cy.runLuaCode({ luaCode: "return 40 + 2" }).then(result => {
expect(result.value).to.equal(42)
})

// can return strings
cy.runLuaCode({ luaCode: "return 'hello from lua'" }).then(result => {
expect(result.value).to.equal("hello from lua")
})

// can use nvim apis
cy.runLuaCode({ luaCode: "return vim.api.nvim_get_current_win()" }).then(result => {
expect(result.value).to.equal(1000)
})

// does not return anything if the lua code does not return anything.
// But side effects are visible in the neovim instance.
cy.runLuaCode({ luaCode: `vim.api.nvim_command('echo "hello from lua"')` }).then(result => {
expect(result.value).to.equal(null)
})
cy.contains("hello from lua")

// can programmatically manipulate the buffer
cy.runLuaCode({
luaCode: `
vim.api.nvim_buf_set_lines(0, 0, -1, false, {'hello', 'from', 'lua'})
`,
})
cy.contains("If you see this text, Neovim is ready!").should("not.exist")
cy.contains("hello")
cy.contains("from")
cy.contains("lua")

// can return tables
cy.runLuaCode({ luaCode: "return {1, 2, 3}" }).then(result => {
expect(result.value).to.deep.equal([1, 2, 3])
})

// can return nested tables
cy.runLuaCode({ luaCode: "return {1, {2, 3}, 4}" }).then(result => {
expect(result.value).to.deep.equal([1, [2, 3], 4])
})
})
})
})
12 changes: 11 additions & 1 deletion packages/integration-tests/cypress/support/tui-sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
//
// This file is autogenerated by tui-sandbox. Do not edit it directly.
//
import type { BlockingCommandClientInput } from "@tui-sandbox/library/dist/src/server/server"
import type { BlockingCommandClientInput, LuaCodeClientInput } from "@tui-sandbox/library/dist/src/server/server"
import type {
BlockingShellCommandOutput,
RunLuaCodeOutput,
StartNeovimGenericArguments,
} from "@tui-sandbox/library/dist/src/server/types"
import type { OverrideProperties } from "type-fest"
Expand All @@ -19,6 +20,7 @@ declare global {
interface Window {
startNeovim(startArguments?: MyStartNeovimServerArguments): Promise<NeovimContext>
runBlockingShellCommand(input: BlockingCommandClientInput): Promise<BlockingShellCommandOutput>
runLuaCode(input: LuaCodeClientInput): Promise<RunLuaCodeOutput>
}
}

Expand Down Expand Up @@ -49,6 +51,12 @@ Cypress.Commands.add("typeIntoTerminal", (text: string, options?: Partial<Cypres
cy.get("textarea").focus().type(text, options)
})

Cypress.Commands.add("runLuaCode", (input: LuaCodeClientInput) => {
cy.window().then(async win => {
return await win.runLuaCode(input)
})
})

before(function () {
// disable Cypress's default behavior of logging all XMLHttpRequests and
// fetches to the Command Log
Expand All @@ -68,6 +76,8 @@ declare global {
/** Runs a shell command in a blocking manner, waiting for the command to
* finish before returning. Requires neovim to be running. */
runBlockingShellCommand(input: BlockingCommandClientInput): Chainable<BlockingShellCommandOutput>

runLuaCode(input: LuaCodeClientInput): Chainable<RunLuaCodeOutput>
}
}
}
14 changes: 12 additions & 2 deletions packages/library/src/browser/neovim-client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { TerminalClient } from "../client/index.js"
import type { BlockingCommandClientInput } from "../server/server.js"
import type { BlockingShellCommandOutput, StartNeovimGenericArguments, TestDirectory } from "../server/types.js"
import type { BlockingCommandClientInput, LuaCodeClientInput } from "../server/server.js"
import type {
BlockingShellCommandOutput,
RunLuaCodeOutput,
StartNeovimGenericArguments,
TestDirectory,
} from "../server/types.js"

const app = document.querySelector<HTMLElement>("#app")
if (!app) {
Expand All @@ -26,9 +31,14 @@ window.runBlockingShellCommand = async function (
return client.runBlockingShellCommand(input)
}

window.runLuaCode = async function (input: LuaCodeClientInput): Promise<RunLuaCodeOutput> {
return client.runLuaCode(input)
}

declare global {
interface Window {
startNeovim(startArguments?: StartNeovimGenericArguments): Promise<TestDirectory>
runBlockingShellCommand(input: BlockingCommandClientInput): Promise<BlockingShellCommandOutput>
runLuaCode(input: LuaCodeClientInput): Promise<RunLuaCodeOutput>
}
}
17 changes: 15 additions & 2 deletions packages/library/src/client/terminal-client.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { createTRPCClient, httpBatchLink, splitLink, unstable_httpSubscriptionLink } from "@trpc/client"
import type { Terminal } from "@xterm/xterm"
import "@xterm/xterm/css/xterm.css"
import type { AppRouter, BlockingCommandClientInput } from "../server/server.js"
import type { BlockingShellCommandOutput, StartNeovimGenericArguments, TestDirectory } from "../server/types.js"
import type { AppRouter, BlockingCommandClientInput, LuaCodeClientInput } from "../server/server.js"
import type {
BlockingShellCommandOutput,
RunLuaCodeOutput,
StartNeovimGenericArguments,
TestDirectory,
} from "../server/types.js"
import "./style.css"
import { getTabId, startTerminal } from "./websocket-client.js"

Expand Down Expand Up @@ -92,4 +97,12 @@ export class TerminalClient {
tabId: this.tabId,
})
}

public async runLuaCode(input: LuaCodeClientInput): Promise<RunLuaCodeOutput> {
await this.ready
return this.trpc.neovim.runLuaCode.mutate({
luaCode: input.luaCode,
tabId: this.tabId,
})
}
}
12 changes: 11 additions & 1 deletion packages/library/src/server/cypress-support/contents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ it("should return the expected contents", async () => {
//
// This file is autogenerated by tui-sandbox. Do not edit it directly.
//
import type { BlockingCommandClientInput } from "@tui-sandbox/library/dist/src/server/server"
import type { BlockingCommandClientInput, LuaCodeClientInput } from "@tui-sandbox/library/dist/src/server/server"
import type {
BlockingShellCommandOutput,
RunLuaCodeOutput,
StartNeovimGenericArguments,
} from "@tui-sandbox/library/dist/src/server/types"
import type { OverrideProperties } from "type-fest"
Expand All @@ -24,6 +25,7 @@ it("should return the expected contents", async () => {
interface Window {
startNeovim(startArguments?: MyStartNeovimServerArguments): Promise<NeovimContext>
runBlockingShellCommand(input: BlockingCommandClientInput): Promise<BlockingShellCommandOutput>
runLuaCode(input: LuaCodeClientInput): Promise<RunLuaCodeOutput>
}
}
Expand Down Expand Up @@ -54,6 +56,12 @@ it("should return the expected contents", async () => {
cy.get("textarea").focus().type(text, options)
})
Cypress.Commands.add("runLuaCode", (input: LuaCodeClientInput) => {
cy.window().then(async win => {
return await win.runLuaCode(input)
})
})
before(function () {
// disable Cypress's default behavior of logging all XMLHttpRequests and
// fetches to the Command Log
Expand All @@ -73,6 +81,8 @@ it("should return the expected contents", async () => {
/** Runs a shell command in a blocking manner, waiting for the command to
* finish before returning. Requires neovim to be running. */
runBlockingShellCommand(input: BlockingCommandClientInput): Chainable<BlockingShellCommandOutput>
runLuaCode(input: LuaCodeClientInput): Chainable<RunLuaCodeOutput>
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions packages/library/src/server/cypress-support/contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ const __filename = fileURLToPath(import.meta.url)
export async function createCypressSupportFileContents(): Promise<string> {
// this is the interface of tui-sandbox as far as cypress in the user's
// application is concerned
let text = `
let text = `
/// <reference types="cypress" />
//
// This file is autogenerated by tui-sandbox. Do not edit it directly.
//
import type { BlockingCommandClientInput } from "@tui-sandbox/library/dist/src/server/server"
import type { BlockingCommandClientInput, LuaCodeClientInput } from "@tui-sandbox/library/dist/src/server/server"
import type {
BlockingShellCommandOutput,
RunLuaCodeOutput,
StartNeovimGenericArguments,
} from "@tui-sandbox/library/dist/src/server/types"
import type { OverrideProperties } from "type-fest"
Expand All @@ -28,6 +29,7 @@ declare global {
interface Window {
startNeovim(startArguments?: MyStartNeovimServerArguments): Promise<NeovimContext>
runBlockingShellCommand(input: BlockingCommandClientInput): Promise<BlockingShellCommandOutput>
runLuaCode(input: LuaCodeClientInput): Promise<RunLuaCodeOutput>
}
}
Expand Down Expand Up @@ -58,6 +60,12 @@ Cypress.Commands.add("typeIntoTerminal", (text: string, options?: Partial<Cypres
cy.get("textarea").focus().type(text, options)
})
Cypress.Commands.add("runLuaCode", (input: LuaCodeClientInput) => {
cy.window().then(async win => {
return await win.runLuaCode(input)
})
})
before(function () {
// disable Cypress's default behavior of logging all XMLHttpRequests and
// fetches to the Command Log
Expand All @@ -77,6 +85,8 @@ declare global {
/** Runs a shell command in a blocking manner, waiting for the command to
* finish before returning. Requires neovim to be running. */
runBlockingShellCommand(input: BlockingCommandClientInput): Chainable<BlockingShellCommandOutput>
runLuaCode(input: LuaCodeClientInput): Chainable<RunLuaCodeOutput>
}
}
}
Expand Down
35 changes: 33 additions & 2 deletions packages/library/src/server/neovim/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import assert from "assert"
import { exec } from "child_process"
import "core-js/proposals/async-explicit-resource-management.js"
import util from "util"
import type { BlockingCommandInput } from "../server.js"
import type { BlockingShellCommandOutput, StartNeovimGenericArguments, TestDirectory } from "../types.js"
import type { BlockingCommandInput, LuaCodeInput } from "../server.js"
import type {
BlockingShellCommandOutput,
RunLuaCodeOutput,
StartNeovimGenericArguments,
TestDirectory,
} from "../types.js"
import type { TestServerConfig } from "../updateTestdirectorySchemaFile.js"
import { convertEventEmitterToAsyncGenerator } from "../utilities/generator.js"
import type { TabId } from "../utilities/tabId.js"
Expand Down Expand Up @@ -110,3 +115,29 @@ export async function runBlockingShellCommand(
throw new Error(`Error running shell blockingCommand (${input.command})`, { cause: e })
}
}

export async function runLuaCode(options: LuaCodeInput): Promise<RunLuaCodeOutput> {
const neovim = neovims.get(options.tabId.tabId)
assert(
neovim !== undefined,
`Neovim instance for clientId not found - cannot run Lua code. Maybe neovim's not started yet?`
)
assert(
neovim.application,
`Neovim application not found for client id ${options.tabId.tabId}. Maybe it's not started yet?`
)

const api = await neovim.state?.client.get()
if (!api) {
throw new Error(`Neovim API not available for client id ${options.tabId.tabId}. Maybe it's not started yet?`)
}

console.log(`Neovim ${neovim.application.processId()} running Lua code: ${options.luaCode}`)
try {
const value = await api.lua(options.luaCode)
return { value }
} catch (e) {
console.warn(`Error running Lua code: ${options.luaCode}`, e)
throw new Error(`Error running Lua code: ${options.luaCode}`, { cause: e })
}
}
8 changes: 8 additions & 0 deletions packages/library/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const blockingCommandInputSchema = z.object({
export type BlockingCommandClientInput = Except<BlockingCommandInput, "tabId">
export type BlockingCommandInput = z.infer<typeof blockingCommandInputSchema>

const luaCodeInputSchema = z.object({ tabId: tabIdSchema, luaCode: z.string() })
export type LuaCodeClientInput = Except<LuaCodeInput, "tabId">
export type LuaCodeInput = z.infer<typeof luaCodeInputSchema>

/** @private */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export async function createAppRouter(config: TestServerConfig) {
Expand Down Expand Up @@ -72,6 +76,10 @@ export async function createAppRouter(config: TestServerConfig) {
runBlockingShellCommand: trpc.procedure.input(blockingCommandInputSchema).mutation(async options => {
return neovim.runBlockingShellCommand(options.signal, options.input, options.input.allowFailure ?? false)
}),

runLuaCode: trpc.procedure.input(luaCodeInputSchema).mutation(options => {
return neovim.runLuaCode(options.input)
}),
}),
})

Expand Down
7 changes: 7 additions & 0 deletions packages/library/src/server/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { VimValue } from "neovim/lib/types/VimValue.js"

/** Describes the contents of the test directory, which is a blueprint for
* files and directories. Tests can create a unique, safe environment for
* interacting with the contents of such a directory.
Expand Down Expand Up @@ -42,3 +44,8 @@ export type BlockingShellCommandOutput =
// for now we log the error to the server's console output. It will be
// visible when running the tests.
}

export type RunLuaCodeOutput = {
value?: VimValue
// to catch errors, use pcall() in the Lua code
}

0 comments on commit 04ac8eb

Please sign in to comment.