Merge branch 'main' into pokey/add-shell-check-pre-commit-hook

This commit is contained in:
Phil Cohen 2024-08-02 18:25:34 -07:00 committed by GitHub
commit 57368e7848
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3688 changed files with 32130 additions and 18515 deletions

View File

@ -4,12 +4,15 @@
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
// We want to enable this in the long run. For now there are a lot of errors that needs to be handled.
// "plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:import/typescript",
"prettier"
],
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
"sourceType": "module",
"project": true
},
"plugins": [
"@typescript-eslint",
@ -20,6 +23,7 @@
],
"rules": {
"import/no-relative-packages": "error",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/consistent-type-assertions": [
"error",
{
@ -37,21 +41,22 @@
}
}
],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"unused-imports/no-unused-imports": "error",
"@typescript-eslint/no-unused-vars": [
"warn",
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"ignoreRestSiblings": true
}
],
"curly": "warn",
"curly": "error",
"eqeqeq": [
"warn",
"error",
"always",
{
"null": "never"
@ -67,7 +72,7 @@
"error",
"MemberExpression[object.property.name='constructor'][property.name='name']"
],
"no-throw-literal": "warn",
"no-throw-literal": "error",
"semi": "off",
"unicorn/prefer-module": "error",
"mocha/no-skipped-tests": "error",
@ -85,21 +90,30 @@
}
]
}
},
{
"files": [
"jest.config.ts",
"docusaurus.config.mts",
"mdx-components.tsx",
"typings/**"
],
"extends": ["plugin:@typescript-eslint/disable-type-checked"]
}
],
"settings": {
"import/resolver": {
"typescript": {
"alwaysTryTypes": true, // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`
"project": ["tsconfig.json", "packages/*/tsconfig.json"]
}
}
},
"ignorePatterns": [
"**/vendor/**/*.ts",
"**/vendor/**/*.js",
"**/generated/**",
"**/out/**",
"**/generated/**"
"**/vendor/**/*.js",
"**/vendor/**/*.ts",
"/data/playground/**"
]
}

11
.github/CODEOWNERS vendored
View File

@ -1,4 +1,9 @@
* @pokey @AndreasArvidsson @phillco
* @cursorless-dev/code-owners
*keyboard* @pokey @AndreasArvidsson @phillco @josharian
*Keyboard* @pokey @AndreasArvidsson @phillco @josharian
*keyboard* @cursorless-dev/code-owners @josharian
*Keyboard* @cursorless-dev/code-owners @josharian
*neovim* @cursorless-dev/code-owners @saidelike @fidgetingbits
*Neovim* @cursorless-dev/code-owners @saidelike @fidgetingbits
*nvim* @cursorless-dev/code-owners @saidelike @fidgetingbits
*Nvim* @cursorless-dev/code-owners @saidelike @fidgetingbits

13
.github/actions/lint-lua-ls/action.yml vendored Normal file
View File

@ -0,0 +1,13 @@
name: "Lua Language Server Lint"
description: "Lints all lua files with lua-language-server"
runs:
using: "composite"
steps:
- uses: cachix/install-nix-action@v27
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: DeterminateSystems/magic-nix-cache-action@v2
- run: nix profile install --accept-flake-config .#lua-language-server
shell: bash
- run: scripts/lint-lua-ls.sh
shell: bash

View File

@ -0,0 +1,17 @@
name: "Neovim Lua Tests"
description: "Set up Neovim Lua environment and run Busted tests"
runs:
using: "composite"
steps:
- uses: leafo/gh-actions-lua@v9
with:
luaVersion: "luajit-2.1.0-beta3"
- uses: leafo/gh-actions-luarocks@v4
- shell: bash
run: |
luarocks install busted
luarocks install luafilesystem
- shell: bash
run: |
cd cursorless.nvim
busted --run unit

View File

@ -40,6 +40,46 @@ jobs:
registryUrl: https://marketplace.visualstudio.com
extensionFile: ${{ steps.publishToOpenVSX.outputs.vsixPath }}
publish-neovim-extension:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
environment: production
env:
CURSORLESS_REPO_ROOT: ${{ github.workspace }}
STAGING_DIRECTORY: ${{ github.workspace }}/cursorless.nvim-staging
steps:
- uses: actions/checkout@v4
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm --color install
- run: pnpm --color compile
- run: pnpm --color --filter '!cursorless-org' --filter '!cursorless-org-*' build
env:
CURSORLESS_DEPLOY: true
- uses: actions/checkout@v4
with:
token: ${{ secrets.CURSORLESS_BOT_TOKEN }}
repository: hands-free-vim/cursorless.nvim
path: ${{ env.STAGING_DIRECTORY }}
- name: Configure GPG Key
working-directory: ${{ env.STAGING_DIRECTORY }}
run: |
echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --import
env:
GPG_SIGNING_KEY: ${{ secrets.CURSORLESS_BOT_GPG_SIGNING_KEY }}
- name: git config
working-directory: ${{ env.STAGING_DIRECTORY }}
run: |
git config user.name cursorless-bot
git config user.email 98099035+cursorless-bot@users.noreply.github.com
git config user.signingkey A9387720AFC62221
git config commit.gpgsign true
- name: Push compiled files to cursorless.nvim plugin repo
run: bash -x scripts/deploy-cursorless-nvim.sh ${{ env.STAGING_DIRECTORY }}
push-cursorless-talon:
name: Push cursorless-talon subrepo
runs-on: ubuntu-latest

View File

@ -8,11 +8,14 @@ on:
types: [opened, synchronize, reopened]
merge_group:
branches: [main]
workflow_dispatch:
jobs:
pre-commit:
name: Pre-commit
runs-on: ubuntu-latest
env:
CURSORLESS_REPO_ROOT: ${{ github.workspace }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
@ -24,6 +27,8 @@ jobs:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm --color install
- uses: leafo/gh-actions-lua@v9
- uses: leafo/gh-actions-luarocks@v4
- uses: pre-commit/action@v3.0.1
- uses: pre-commit-ci/lite-action@v1.0.2
if: always()

View File

@ -17,16 +17,19 @@ jobs:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
vscode_version: [stable]
app_version: [stable]
include:
- os: ubuntu-latest
vscode_version: legacy
app_version: legacy
runs-on: ${{ matrix.os }}
env:
VSCODE_VERSION: ${{ matrix.vscode_version }}
APP_VERSION: ${{ matrix.app_version }}
NEOVIM_VERSION: ${{ matrix.app_version == 'stable' && 'stable' || 'v0.10.0' }}
VSCODE_CRASH_DIR: ${{ github.workspace }}/artifacts/dumps
VSCODE_LOGS_DIR: ${{ github.workspace }}/artifacts/logs
CURSORLESS_REPO_ROOT: ${{ github.workspace }}
TEMP_DIR: ${{ github.workspace }}/temp
NODE_OPTIONS: "--max-old-space-size=4096"
steps:
- uses: actions/checkout@v4
- run: corepack enable
@ -34,7 +37,7 @@ jobs:
with:
node-version-file: .nvmrc
cache: pnpm
- run: mkdir -p "${{ env.VSCODE_CRASH_DIR }}" "${{ env.VSCODE_LOGS_DIR }}"
- run: mkdir -p "${{ env.VSCODE_CRASH_DIR }}" "${{ env.VSCODE_LOGS_DIR }}" "${{ env.TEMP_DIR }}"
shell: bash
- run: pnpm --color install
- run: pnpm --color compile
@ -43,9 +46,32 @@ jobs:
if: runner.os == 'Linux'
- run: pnpm --color test
if: runner.os != 'Linux'
- run: xvfb-run -a pnpm -F @cursorless/test-harness test:talonJs
if: runner.os == 'Linux' && matrix.vscode_version == 'stable'
- run: pnpm -F @cursorless/test-harness test:talonJs
if: runner.os != 'Linux' && matrix.vscode_version == 'stable'
- run: xvfb-run -a pnpm -F @cursorless/cursorless-everywhere-talon-e2e test:quickjs
if: runner.os == 'Linux' && matrix.vscode_version == 'stable'
- run: pnpm -F @cursorless/cursorless-everywhere-talon-e2e test:quickjs
if: runner.os != 'Linux' && matrix.vscode_version == 'stable'
- run: bash -x scripts/install-neovim-dependencies.sh
- uses: rhysd/action-setup-vim@v1
id: vim
with:
version: ${{ env.NEOVIM_VERSION }}
neovim: true
- name: Run neovim tests
run: xvfb-run -a pnpm -F @cursorless/test-harness test:neovim
if: runner.os == 'Linux'
env:
NEOVIM_PATH: ${{ steps.vim.outputs.executable }}
- uses: ./.github/actions/test-neovim-lua/
if: runner.os == 'Linux' && matrix.app_version == 'stable'
- uses: ./.github/actions/lint-lua-ls/
if: runner.os == 'Linux' && matrix.app_version == 'stable'
- name: Create vscode dist that can be installed locally
run: pnpm -F @cursorless/cursorless-vscode populate-dist --local-install
if: runner.os == 'Linux' && matrix.vscode_version == 'stable'
if: runner.os == 'Linux' && matrix.app_version == 'stable'
- name: Test create vsix
id: createVsix
uses: HaaLeo/publish-vscode-extension@v1
@ -54,10 +80,10 @@ jobs:
packagePath: packages/cursorless-vscode/dist
dryRun: true
- run: mv ${{ steps.createVsix.outputs.vsixPath }} cursorless-development.vsix
if: runner.os == 'Linux' && matrix.vscode_version == 'stable'
if: runner.os == 'Linux' && matrix.app_version == 'stable'
- name: Upload vsix
uses: actions/upload-artifact@v4
if: runner.os == 'Linux' && matrix.vscode_version == 'stable'
if: runner.os == 'Linux' && matrix.app_version == 'stable'
with:
name: vsix
path: cursorless-development.vsix

7
.gitignore vendored
View File

@ -1,10 +1,13 @@
out
dist
testOut
node_modules
.vscode-test/
*.vsix
/package-lock.json
*.DS_Store
meta.json
.luacheckcache
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
@ -43,5 +46,9 @@ next-env.d.ts
# test subset config
packages/test-harness/testSubsetGrep.properties
# cursorless-neovim
cursorless.nvim/node/cursorless-neovim
cursorless.nvim/node/test-harness
# nix
.direnv/

8
.luacheckrc Normal file
View File

@ -0,0 +1,8 @@
std = luajit
cache = true
codes = true
ignore = { "432" }
globals = {
"vim",
}

6
.luarc.json Normal file
View File

@ -0,0 +1,6 @@
{
"runtime.version": "Lua 5.1",
"diagnostics.ignoredFiles": "Disable",
"diagnostics.globals": ["vim", "talon", "it", "describe"],
"workspace.ignoreDir": ["data/playground/lua/", ".luarocks", ".lua"]
}

View File

@ -1,7 +1,7 @@
minimum_pre_commit_version: "2.9.0"
ci:
autoupdate_schedule: monthly
exclude: /vendor/
exclude: /vendor/|^data/playground/
repos:
- repo: meta
hooks:
@ -81,6 +81,14 @@ repos:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/lunarmodules/luacheck
rev: v1.2.0
hooks:
- id: luacheck
- repo: https://github.com/JohnnyMorganz/StyLua
rev: v0.20.0
hooks:
- id: stylua
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.10.0
hooks:

View File

@ -7,7 +7,9 @@
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"jrieken.vscode-tree-sitter-query",
"wenkokke.tree-sitter-talon",
"usernamehw.commands"
"wenkokke.talonfmt-vscode",
"usernamehw.commands",
"sumneko.lua",
"JohnnyMorganz.stylua"
]
}

57
.vscode/launch.json vendored
View File

@ -110,9 +110,27 @@
]
},
// Neovim launch configs
{
"name": "Neovim: Run",
"request": "attach",
"continueOnAttach": true,
"skipFiles": ["<node_internals>/**"],
"preLaunchTask": "Neovim: Build extension",
"type": "node"
},
{
"name": "Neovim: Test",
"request": "attach",
"continueOnAttach": true,
"skipFiles": ["<node_internals>/**"],
"preLaunchTask": "Neovim: Build extension and tests",
"type": "node"
},
// Talon launch configs
{
"name": "Talon: Test",
"name": "Talon: Test grammar",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/packages/test-harness/dist/runTalonTests.cjs",
@ -128,7 +146,7 @@
]
},
{
"name": "Talon: Test (subset)",
"name": "Talon: Test grammar (subset)",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/packages/test-harness/dist/runTalonTests.cjs",
@ -145,6 +163,41 @@
]
},
// Talon everywhere/JS launch configs
{
"name": "TalonJS: Test",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/packages/test-harness/dist/runTalonJsTests.cjs",
"env": {
"CURSORLESS_MODE": "test",
"CURSORLESS_REPO_ROOT": "${workspaceFolder}"
},
"outFiles": ["${workspaceFolder}/**/out/**/*.js"],
"preLaunchTask": "VSCode: Build extension and tests",
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
},
{
"name": "TalonJS: Test (subset)",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/packages/test-harness/dist/runTalonJsTests.cjs",
"env": {
"CURSORLESS_MODE": "test",
"CURSORLESS_RUN_TEST_SUBSET": "true",
"CURSORLESS_REPO_ROOT": "${workspaceFolder}"
},
"outFiles": ["${workspaceFolder}/**/out/**/*.js"],
"preLaunchTask": "VSCode: Build extension and tests",
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
},
// Unit tests launch configs
{
"name": "Unit tests: Test",

View File

@ -25,5 +25,11 @@
"files.eol": "\n",
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.tsdk": "node_modules/typescript/lib",
"eslint.workingDirectories": [{ "pattern": "packages/*/" }]
"eslint.workingDirectories": [{ "pattern": "packages/*/" }],
"[lua]": {
"editor.defaultFormatter": "JohnnyMorganz.stylua"
},
"files.associations": {
".busted": "lua"
}
}

146
.vscode/tasks.json vendored
View File

@ -152,6 +152,152 @@
"group": "test"
},
// Neovim tasks
{
"label": "Neovim: Build extension",
"dependsOn": [
"Neovim: Launch neovim",
"Neovim: ESBuild",
"Neovim: Populate dist"
],
"group": "build"
},
{
"label": "Neovim: Build extension and tests",
"dependsOn": [
"Neovim: Launch neovim (test)",
"Neovim: ESBuild",
"Neovim: Populate dist",
"TSBuild",
"Build test harness"
],
"group": "build"
},
{
"label": "Neovim: ESBuild",
"type": "npm",
"script": "esbuild",
"path": "packages/cursorless-neovim",
"dependsOn": ["Generate grammar"],
"presentation": {
"reveal": "silent"
},
"group": "build"
},
{
"label": "Neovim: Populate dist",
"type": "npm",
"script": "populate-dist",
"path": "packages/cursorless-neovim",
"presentation": {
"reveal": "silent"
},
"options": {
"env": {
"CURSORLESS_REPO_ROOT": "${workspaceFolder}"
}
},
"group": "build"
},
{
"label": "Neovim: Launch neovim",
"type": "process",
"command": "packages/cursorless-neovim/scripts/linux-terminal.sh",
"args": [
"${workspaceFolder}/packages/cursorless-neovim/scripts/debug-neovim.sh ${workspaceFolder} development"
],
"osx": {
"command": "osascript",
"args": [
"-e",
"tell app \"Terminal\" to do script \"${workspaceFolder}/packages/cursorless-neovim/scripts/debug-neovim.sh ${workspaceFolder} development\" activate"
]
},
"windows": {
"command": "powershell",
"args": [
"(New-Object -ComObject WScript.Shell).Run(\"\"\"${workspaceFolder}/packages/cursorless-neovim/scripts/debug-neovim.bat\"\"\", 1, $false)"
]
},
"group": "build",
"presentation": {
"reveal": "silent"
},
"options": {
"env": {
"CURSORLESS_REPO_ROOT": "${workspaceFolder}",
"NVIM_NODE_HOST_DEBUG": "1",
"NVIM_NODE_LOG_FILE": "${workspaceFolder}/packages/cursorless-neovim/out/nvim_node.log",
"NVIM_NODE_LOG_LEVEL": "info",
"CURSORLESS_MODE": "development"
}
}
},
{
"label": "Neovim: Launch neovim (test)",
"type": "process",
"command": "packages/cursorless-neovim/scripts/linux-terminal.sh",
"args": [
"${workspaceFolder}/packages/cursorless-neovim/scripts/debug-neovim.sh ${workspaceFolder} test"
],
"osx": {
"command": "osascript",
"args": [
"-e",
"tell app \"Terminal\" to do script \"${workspaceFolder}/packages/cursorless-neovim/scripts/debug-neovim.sh ${workspaceFolder} test\" activate"
]
},
"windows": {
"command": "powershell",
"args": [
"(New-Object -ComObject WScript.Shell).Run(\"\"\"${workspaceFolder}/packages/cursorless-neovim/scripts/debug-neovim.bat\"\"\", 1, $false)"
]
},
"group": "build",
"presentation": {
"reveal": "silent"
},
"options": {
"env": {
"CURSORLESS_REPO_ROOT": "${workspaceFolder}",
"NVIM_NODE_HOST_DEBUG": "1",
"NVIM_NODE_LOG_FILE": "${workspaceFolder}/packages/cursorless-neovim/out/nvim_node.log",
"NVIM_NODE_LOG_LEVEL": "info",
"CURSORLESS_MODE": "test"
}
}
},
{
"label": "Neovim: Show logs",
"type": "shell",
"command": "packages/cursorless-neovim/scripts/show-logs.sh",
"problemMatcher": [],
"isBackground": true,
"presentation": {
"echo": false,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": false,
"clear": true
},
"options": {
"env": {
"CURSORLESS_REPO_ROOT": "${workspaceFolder}"
}
}
// NOTE: We don't have a way on Windows atm due to command with argument inside Run() not working
// so we need to show logs outside of vscode (see #2454)
},
{
"label": "Neovim: Launch neovim (lua test)",
"type": "shell",
"command": "busted --run unit",
"options": {
"cwd": "cursorless.nvim"
}
},
// cursorless.org
{
"label": "Serve cursorless.org",

View File

@ -0,0 +1,50 @@
from typing import TypedDict
from talon import Module
class SelectionOffsets(TypedDict):
anchor: int
active: int
class EditorState(TypedDict):
text: str
selections: list[SelectionOffsets]
class EditorChange(TypedDict):
text: str
rangeOffset: int
rangeLength: int
class EditorEdit(TypedDict):
# The new document content after the edit
text: str
# A list of changes that were made to the document. If you can not handle
# this, you can ignore it and just replace the entire document with the
# value of the `text` field above.
changes: list[EditorChange]
mod = Module()
mod.tag("cursorless_everywhere_talon", desc="Enable cursorless everywhere in Talon")
@mod.action_class
class Actions:
def cursorless_everywhere_get_editor_state() -> EditorState: # pyright: ignore [reportReturnType]
"""Get the focused editor element state"""
def cursorless_everywhere_set_selections(
selections: list[SelectionOffsets], # pyright: ignore [reportGeneralTypeIssues]
):
"""Set focused element selections"""
def cursorless_everywhere_edit_text(
edit: EditorEdit, # pyright: ignore [reportGeneralTypeIssues]
):
"""Edit focused element text"""

View File

@ -0,0 +1,129 @@
from talon import Context, app, ui
from .cursorless_everywhere_talon import (
EditorEdit,
EditorState,
SelectionOffsets,
)
if app.platform == "windows":
from talon.windows.ax import TextRange
# https://learn.microsoft.com/en-us/dotnet/api/system.windows.automation.text.textpatternrange?view=windowsdesktop-8.0
ctx = Context()
ctx.matches = r"""
os: windows
"""
@ctx.action_class("user")
class Actions:
def cursorless_everywhere_get_editor_state() -> EditorState:
el = ui.focused_element()
if "Text2" not in el.patterns:
raise ValueError("Focused element is not a text element")
text_pattern = el.text_pattern2
document_range = text_pattern.document_range
caret_range = text_pattern.caret_range
selection_ranges = text_pattern.selection
selections: list[SelectionOffsets] = []
for selection_range in selection_ranges:
anchor, active = get_selection(document_range, selection_range, caret_range)
selections.append(
{
"anchor": anchor,
"active": active,
}
)
return {
"text": document_range.text,
"selections": selections,
}
def cursorless_everywhere_set_selections(
selections: list[SelectionOffsets], # pyright: ignore [reportGeneralTypeIssues]
):
if selections.length != 1: # pyright: ignore [reportAttributeAccessIssue]
raise ValueError("Only single selection supported")
selection = selections[0]
anchor = selection["anchor"]
active = selection["active"]
el = ui.focused_element()
if "Text2" not in el.patterns:
raise ValueError("Focused element is not a text element")
text_pattern = el.text_pattern2
document_range = text_pattern.document_range
set_selection(document_range, anchor, active)
def cursorless_everywhere_edit_text(
edit: EditorEdit, # pyright: ignore [reportGeneralTypeIssues]
):
text = edit["text"]
el = ui.focused_element()
if "Value" not in el.patterns:
raise ValueError("Focused element is not a text element")
el.value_pattern.value = text
def set_selection(document_range: TextRange, anchor: int, active: int):
# This happens in slack, for example. The document range starts with a
# newline and selecting first character we'll make the selection go outside
# of the edit box.
if document_range.text.startswith("\n") and anchor == 0 and active == 0:
anchor = 1
active = 1
start = min(anchor, active)
end = max(anchor, active)
range = document_range.clone()
range.move_endpoint_by_range("End", "Start", target=document_range)
range.move_endpoint_by_unit("End", "Character", end)
range.move_endpoint_by_unit("Start", "Character", start)
range.select()
def get_selection(
document_range: TextRange, selection_range: TextRange, caret_range: TextRange
) -> tuple[int, int]:
# Make copy of the document range to avoid modifying the original
range_before_selection = document_range.clone()
# Move the end of the copy to the start of the selection
# range_before_selection.end = selection_range.start
range_before_selection.move_endpoint_by_range(
"End",
"Start",
target=selection_range,
)
# The selection start offset is the length of the text before the selection
start = len(range_before_selection.text)
range_after_selection = document_range.clone()
range_after_selection.move_endpoint_by_range(
"Start",
"End",
target=selection_range,
)
end = len(document_range.text) - len(range_after_selection.text)
# The selection is reversed if the caret is at the start of the selection
is_reversed = (
caret_range.compare_endpoints("Start", "Start", target=selection_range) == 0
)
# Return as (anchor, active)
return (end, start) if is_reversed else (start, end)

View File

@ -23,3 +23,12 @@ debug edit subset:
debug {user.cursorless_launch_configuration}:
user.run_rpc_command("commands.startDebugging", cursorless_launch_configuration)
user.run_rpc_command("workbench.debug.action.focusRepl")
neovim log:
user.run_rpc_command("workbench.action.tasks.runTask", "Neovim: Show logs")
debug neovim:
user.run_rpc_command("commands.startDebugging", "Neovim: Run")
user.run_rpc_command("workbench.action.tasks.showTasks")
debug test neovim:
user.run_rpc_command("commands.startDebugging", "Neovim: Test")
user.run_rpc_command("workbench.action.tasks.showTasks")

View File

@ -27,7 +27,7 @@ head_tail_swallowed_modifiers = [
"<user.cursorless_simple_scope_modifier>", # funk, state, class, every funk
"<user.cursorless_ordinal_scope>", # first past second word
"<user.cursorless_relative_scope>", # next funk, 3 funks
"<user.cursorless_surrounding_pair>", # matching/pair [curly, round]
"<user.cursorless_surrounding_pair_force_direction>", # DEPRECATED "left quad" / "right quad"
]
modifiers = [

View File

@ -15,7 +15,10 @@ mod.list(
@mod.capture(
rule="{user.cursorless_scope_type} | <user.cursorless_glyph_scope_type> | {user.cursorless_custom_regex_scope_type}"
rule="{user.cursorless_scope_type}"
" | <user.cursorless_surrounding_pair_scope_type>"
" | <user.cursorless_glyph_scope_type>"
" | {user.cursorless_custom_regex_scope_type}"
)
def cursorless_scope_type(m) -> dict[str, str]:
"""Cursorless scope type singular"""
@ -24,16 +27,27 @@ def cursorless_scope_type(m) -> dict[str, str]:
except AttributeError:
pass
try:
return m.cursorless_surrounding_pair_scope_type
except AttributeError:
pass
try:
return m.cursorless_glyph_scope_type
except AttributeError:
pass
return {"type": "customRegex", "regex": m.cursorless_custom_regex_scope_type}
return {
"type": "customRegex",
"regex": m.cursorless_custom_regex_scope_type,
}
@mod.capture(
rule="{user.cursorless_scope_type_plural} | <user.cursorless_glyph_scope_type_plural> | {user.cursorless_custom_regex_scope_type_plural}"
rule="{user.cursorless_scope_type_plural}"
" | <user.cursorless_surrounding_pair_scope_type_plural>"
" | <user.cursorless_glyph_scope_type_plural>"
" | {user.cursorless_custom_regex_scope_type_plural}"
)
def cursorless_scope_type_plural(m) -> dict[str, str]:
"""Cursorless scope type plural"""
@ -42,6 +56,11 @@ def cursorless_scope_type_plural(m) -> dict[str, str]:
except AttributeError:
pass
try:
return m.cursorless_surrounding_pair_scope_type_plural
except AttributeError:
pass
try:
return m.cursorless_glyph_scope_type_plural
except AttributeError:

View File

@ -22,6 +22,10 @@ mod.list(
"cursorless_surrounding_pair_scope_type",
desc="Scope types that can function as surrounding pairs",
)
mod.list(
"cursorless_surrounding_pair_scope_type_plural",
desc="Plural form of scope types that can function as surrounding pairs",
)
@mod.capture(
@ -30,29 +34,43 @@ mod.list(
"{user.cursorless_surrounding_pair_scope_type}"
)
)
def cursorless_surrounding_pair_scope_type(m) -> str:
def cursorless_surrounding_pair_scope_type(m) -> dict[str, str]:
"""Surrounding pair scope type"""
try:
return m.cursorless_surrounding_pair_scope_type
delimiter = m.cursorless_surrounding_pair_scope_type
except AttributeError:
return m.cursorless_selectable_paired_delimiter
delimiter = m.cursorless_selectable_paired_delimiter
return {
"type": "surroundingPair",
"delimiter": delimiter,
}
@mod.capture(
rule="[{user.cursorless_delimiter_force_direction}] <user.cursorless_surrounding_pair_scope_type>"
rule=(
"<user.cursorless_selectable_paired_delimiter_plural> |"
"{user.cursorless_surrounding_pair_scope_type_plural}"
)
)
def cursorless_surrounding_pair(m) -> dict[str, Any]:
"""Expand to containing surrounding pair"""
def cursorless_surrounding_pair_scope_type_plural(m) -> dict[str, str]:
"""Plural surrounding pair scope type"""
try:
surrounding_pair_scope_type = m.cursorless_surrounding_pair_scope_type
delimiter = m.cursorless_surrounding_pair_scope_type_plural
except AttributeError:
surrounding_pair_scope_type = "any"
scope_type = {
delimiter = m.cursorless_selectable_paired_delimiter_plural
return {
"type": "surroundingPair",
"delimiter": surrounding_pair_scope_type,
"delimiter": delimiter,
}
@mod.capture(
rule="{user.cursorless_delimiter_force_direction} <user.cursorless_surrounding_pair_scope_type>"
)
def cursorless_surrounding_pair_force_direction(m) -> dict[str, Any]:
"""DEPRECATED: Expand to containing surrounding pair"""
scope_type = m.cursorless_surrounding_pair_scope_type
with suppress(AttributeError):
scope_type["forceDirection"] = m.cursorless_delimiter_force_direction

View File

@ -15,6 +15,15 @@ mod.list(
desc="A paired delimiter that can be used as a scope type and as a wrapper",
)
mod.list(
"cursorless_selectable_only_paired_delimiter_plural",
desc="Plural form of a paired delimiter that can only be used as a scope type",
)
mod.list(
"cursorless_wrapper_selectable_paired_delimiter_plural",
desc="Plural form of a paired delimiter that can be used as a scope type and as a wrapper",
)
# Maps from the id we use in the spoken form csv to the delimiter strings
paired_delimiters = {
"curlyBrackets": ["{", "}"],
@ -58,3 +67,16 @@ def cursorless_selectable_paired_delimiter(m) -> str:
return m.cursorless_selectable_only_paired_delimiter
except AttributeError:
return m.cursorless_wrapper_selectable_paired_delimiter
@mod.capture(
rule=(
"{user.cursorless_selectable_only_paired_delimiter_plural} |"
"{user.cursorless_wrapper_selectable_paired_delimiter_plural}"
)
)
def cursorless_selectable_paired_delimiter_plural(m) -> str:
try:
return m.cursorless_selectable_only_paired_delimiter_plural
except AttributeError:
return m.cursorless_wrapper_selectable_paired_delimiter_plural

View File

@ -170,6 +170,7 @@
"file": "document",
"paint": "nonWhitespaceSequence",
"short paint": "boundedNonWhitespaceSequence",
"short block": "boundedParagraph",
"link": "url",
"cell": "notebookCell"
},

View File

@ -133,18 +133,29 @@ def update():
handle_csv("target_connectives.csv"),
handle_csv("modifiers.csv"),
handle_csv("positions.csv"),
handle_csv("paired_delimiters.csv"),
handle_csv(
"paired_delimiters.csv",
pluralize_lists=[
"selectable_only_paired_delimiter",
"wrapper_selectable_paired_delimiter",
],
),
handle_csv("special_marks.csv"),
handle_csv("scope_visualizer.csv"),
handle_csv("experimental/experimental_actions.csv"),
handle_csv("experimental/miscellaneous.csv"),
handle_csv(
"modifier_scope_types.csv",
pluralize_lists=["scope_type", "glyph_scope_type"],
pluralize_lists=[
"scope_type",
"glyph_scope_type",
"surrounding_pair_scope_type",
],
extra_allowed_values=[
"private.fieldAccess",
"private.switchStatementSubject",
"textFragment",
"disqualifyDelimiter",
],
default_list_name="scope_type",
),

10
cursorless.nvim/.busted Normal file
View File

@ -0,0 +1,10 @@
return {
_all = {
lua = './test/nvim-shim.sh',
output = "TAP",
["defer-print"] = false,
},
unit = {
ROOT = {'./test/unit/'},
},
}

View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Create issue on cursorless-dev/cursorless
url: https://github.com/cursorless-dev/cursorless/issues/new
about: Please file issues on the main Cursorless repository

View File

@ -0,0 +1,7 @@
# Contributing
Welcome! So glad you've decided to help make Cursorless in Neovim better.
Note that Cursorless is maintained as a monorepo, hosted at [`cursorless`](https://github.com/cursorless-dev/cursorless), and the source of truth for all of the files here lives there, so that's where you'll want to file a PR. We automatically deploy from our monorepo to the [cursorless.nvim repo](https://github.com/hands-free-vim/cursorless.nvim) in CI.
See [the Cursorless neovim contributor docs](https://www.cursorless.org/docs/contributing/cursorless-in-neovim/) to get started.

21
cursorless.nvim/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 fidgetingbits, Cedric Halbronn, Brandon Virgil Rule, et al.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

118
cursorless.nvim/README.md Normal file
View File

@ -0,0 +1,118 @@
<!-- vim-markdown-toc GFM -->
- [cursorless.nvim](#cursorlessnvim)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [1. Install Cursorless neovim plugin](#1-install-cursorless-neovim-plugin)
- [Option A: Lazy installation](#option-a-lazy-installation)
- [Option B: Manual installation](#option-b-manual-installation)
- [2. Tell neovim to run the plugin](#2-tell-neovim-to-run-the-plugin)
- [3. Activate Cursorless commands in Talon](#3-activate-cursorless-commands-in-talon)
- [Configuration](#configuration)
- [Keyboard shortcut](#keyboard-shortcut)
- [Absolute row numbers](#absolute-row-numbers)
- [Frequently asked questions](#frequently-asked-questions)
- [nvim does not support Lazy?](#nvim-does-not-support-lazy)
- [nvim does not find the `neovim` globally installed package?](#nvim-does-not-find-the-neovim-globally-installed-package)
- [Contributors](#contributors)
<!-- vim-markdown-toc -->
# cursorless.nvim
Very experimental Neovim plugin providing partial Cursorless support. We support much of the core functionality, but many features are not yet implemented, in particular hats ([#2567](https://github.com/cursorless-dev/cursorless/issues/2567)) and language-specific scopes ([#2568](https://github.com/cursorless-dev/cursorless/issues/2568)). Expect some rough edges, but please give it a try, and if you like it, consider [contributing](https://www.cursorless.org/docs/contributing/cursorless-in-neovim/)!
## Prerequisites
- [neovim](https://neovim.io/) (>= v0.10.0)
- [Talon voice](https://talonvoice.com/)
- [neovim-talon](https://github.com/hands-free-vim/neovim-talon)
- [node/npm](https://nodejs.org/en)
- [neovim node package](https://github.com/neovim/node-client) (>= 5.1.0 installed globally)
- [talon.nvim](https://github.com/hands-free-vim/talon.nvim) (likely required, unless standalone neovim
GUI (nvim-qt.exe, neovide, etc)
## Installation
### 1. Install Cursorless neovim plugin
Ideally, you want to use a neovim plugin manager like [lazy.nvim](https://github.com/folke/lazy.nvim).
#### Option A: Lazy installation
After the typical [lazy setup](https://github.com/folke/lazy.nvim?tab=readme-ov-file#-installation), you'll have to add the `cursorless.nvim` plugin to your `init.lua`.
```lua
require('lazy').setup({
'hands-free-vim/cursorless.nvim',
})
```
#### Option B: Manual installation
This method is not recommended but you can try directly cloning the plugin into your nvim data folder:
```
git clone https://github.com/hands-free-vim/cursorless.nvim
```
### 2. Tell neovim to run the plugin
If you aren't using a plugin manager that automatically calls setup for you (e.g. it is needed for lazy), you will need this somewhere in your neovim config, e.g. in [init.lua](https://neovim.io/doc/user/lua-guide.html#lua-guide-config):
```lua
require("cursorless").setup()
```
### 3. Activate Cursorless commands in Talon
Add a `.talon` file like the following anywhere in your Talon user directory (e.g. named `cursorless_neovim.talon`):
```talon
app: neovim
-
tag(): user.cursorless
```
## Configuration
### Keyboard shortcut
By default the keyboard shortcut used to communicate with cursorless is `<C-S-f12>`, but this might not work for
everybody and is configurable. You can change it by passing a different value in the configuration options passed to
`setup()`:
```lua
require("cursorless").setup({ shortcut = `<C-Q>`})
```
_IMPORTANT_: If you change this shortcut, be sure to set the corresponding neovim-talon setting. This can be done by
having a `.talon` file somewhere in your talon user directory that contains the following:
```talon
settings():
user.neovim_command_server_shortcut = "ctrl-q"
```
### Absolute row numbers
You MUST currently use absolute row numbers in order to target rows using cursorless. The `talon.nvim` plugin will
configure this automatically, but your own config may be overriding it. Be sure to disable relative numbers.
## Frequently asked questions
### nvim does not support Lazy?
Some Linux package managers ship with a version of `nvim` too old for Lazy. If this is the case, [install nvim](https://github.com/neovim/neovim/blob/master/INSTALL.md) via another method.
### nvim does not find the `neovim` globally installed package?
If you are on Linux, avoid using the snap package for `npm` as it may not be able to globally expose the neovim npm package due to sandboxing. If this is the case, install node via another method (nvm, brew, etc).
## Contributing
Welcome! So glad you've decided to help make Cursorless in Neovim better.
Note that Cursorless is maintained as a monorepo, hosted at [`cursorless`](https://github.com/cursorless-dev/cursorless), and the source of truth for all of the files here lives there, so that's where you'll want to file a PR. We automatically deploy from our monorepo to the [cursorless.nvim repo](https://github.com/hands-free-vim/cursorless.nvim) in CI.
See [the Cursorless neovim contributor docs](https://www.cursorless.org/docs/contributing/cursorless-in-neovim/) to get started.

View File

@ -0,0 +1,27 @@
local M = {}
local function default_shortcut()
if require("cursorless.utils").is_platform_macos() then
return "<C-M-\\>"
end
if require("cursorless.utils").is_platform_windows() then
return "<C-S-F12>"
end
return "<C-`>"
end
local config = {
shortcut = default_shortcut(),
}
function M.set_config(user_config)
return vim.tbl_deep_extend("force", config, user_config or {})
end
function M.get_config()
return config
end
return M

View File

@ -0,0 +1,63 @@
local M = {}
-- Get the first and last visible line of the current window/buffer
-- @see https://vi.stackexchange.com/questions/28471/get-first-and-last-visible-line-from-other-buffer-than-current
-- w0/w$ are indexed from 1, similarly to what is shown in neovim
-- e.g. :lua print(vim.inspect(require('cursorless').window_get_visible_lines()))"
-- window_get_visible_lines
-- { [1] = 28, [2] = 74 }
function M.window_get_visible_lines()
-- print('window_get_visible_lines()')
return { vim.fn.line("w0"), vim.fn.line("w$") }
end
-- Get the coordinates of the current selection
-- To manually test follow these steps:
-- 1. In command mode :vmap <c-a> <Cmd>lua print(vim.inspect(require('cursorless').buffer_get_selection()))<Cr>
-- 2. type "hello" on the first line and "world" on the second line
-- 3. Enter visual mode and select "hello" on the first line and continue selection with "world"
-- on the second line.
-- 4. Hit ctrl+a to show the selection: {1, 1, 2, 5, false}
-- 5. Hit 'o' to swap the cursor position and hit ctrl+a again: {1, 1, 2, 5, true}
--
-- If you want to directly see how it is parsed in the node extension:
-- 1. run in command mode :vmap <c-a> <Cmd>:call CursorlessLoadExtension()<Cr>
-- 2. Select some text and hit ctrl+a
function M.buffer_get_selection()
local start_pos = vim.fn.getpos("v") -- start of visual selection
local start_line, start_col = start_pos[2], start_pos[3]
local end_pos = vim.fn.getpos(".") -- end of visual selection (cursor position)
local end_line, end_col = end_pos[2], end_pos[3]
local reverse = false
local mode = vim.api.nvim_get_mode().mode
-- Invert the values depending on if the cursor is before the start
if end_line < start_line or end_col < start_col then
start_line, start_col, end_line, end_col =
end_line, end_col, start_line, start_col
reverse = true
end
-- See https://github.com/cursorless-dev/cursorless/issues/2537 if you want to add more modes
if mode == "V" then
-- Line and block-based visual modes are line-based, so we don't need to track the columns
start_col = 1
end_col = nil
end
return { start_line, start_col, end_line, end_col, reverse }
end
-- https://github.com/nvim-treesitter/nvim-treesitter/blob/master/lua/nvim-treesitter/ts_utils.lua#L278
-- If you have a buffer with the line: "hello world"
-- :lua require("cursorless.cursorless").select_range(1, 2, 1, 4)
-- will highlight "llo"
-- NOTE: works for any mode (n,i,v,nt) except in t mode
function M.select_range(start_line, start_col, end_line, end_col)
vim.cmd([[silent! normal! :noh]])
vim.api.nvim_win_set_cursor(0, { start_line, start_col })
vim.cmd([[silent! normal v]])
vim.api.nvim_win_set_cursor(0, { end_line, end_col })
end
return M

View File

@ -0,0 +1,107 @@
local M
local function register_functions()
local utils = require("cursorless.utils")
local path = utils.cursorless_nvim_path()
-- revert to using forward slashes as it works when passed to remote#host#RegisterPlugin()
if utils.is_platform_windows() then
path = path:gsub("\\", "/")
end
vim.fn["remote#host#RegisterPlugin"](
"node",
path .. "/node/command-server/",
{
{
type = "function",
name = "CommandServerLoadExtension",
sync = false,
opts = vim.empty_dict(),
},
{
type = "function",
name = "CommandServerRunCommand",
sync = false,
opts = vim.empty_dict(),
},
}
)
vim.fn["remote#host#RegisterPlugin"](
"node",
path .. "/node/cursorless-neovim/",
{
{
type = "function",
name = "CursorlessLoadExtension",
sync = false,
opts = vim.empty_dict(),
},
}
)
vim.fn["remote#host#RegisterPlugin"]("node", path .. "/node/test-harness/", {
{
type = "function",
name = "TestHarnessRun",
sync = false,
opts = vim.empty_dict(),
},
})
end
-- this triggers loading the node process as well as calling one function
-- in the cursorless-neovim, command-server and neovim-registry extensions
-- in order to initialize them
local function load_extensions()
vim.fn.CursorlessLoadExtension()
if os.getenv("CURSORLESS_MODE") == "test" then
-- make sure cursorless is loaded before starting the tests
vim.uv.sleep(1000)
vim.fn.TestHarnessRun()
else
vim.fn.CommandServerLoadExtension()
end
end
-- Cursorless command-server shortcut: CTRL+q
-- https://stackoverflow.com/questions/40504408/can-i-map-a-key-binding-to-a-function-in-vimrc
-- https://stackoverflow.com/questions/7642746/is-there-any-way-to-view-the-currently-mapped-keys-in-vim
-- luacheck:ignore 631
-- https://stackoverflow.com/questions/3776117/what-is-the-difference-between-the-remap-noremap-nnoremap-and-vnoremap-mapping
local function configure_command_server_shortcut(shortcut)
-- these mappings don't change the current mode
-- https://neovim.io/doc/user/api.html#nvim_set_keymap()
-- https://www.reddit.com/r/neovim/comments/pt92qn/mapping_cd_in_terminal_mode/
local modes = { "i", "n", "c", "v", "t" }
for _, mode in ipairs(modes) do
vim.api.nvim_set_keymap(
mode,
shortcut,
"<cmd>lua vim.fn.CommandServerRunCommand()<CR>",
{ noremap = true }
)
end
end
local function setup(user_config)
if vim.fn.has("nvim-0.10.0") == 0 then
vim.api.nvim_err_writeln(
"ERROR: Cursorless requires Neovim 0.10.0 or later"
)
return
end
local config = require("cursorless.config").set_config(user_config)
register_functions()
load_extensions()
configure_command_server_shortcut(config.shortcut)
end
local cursorless = require("cursorless.cursorless")
M = {
setup = setup,
config = require("cursorless.config").get_config,
window_get_visible_lines = cursorless.window_get_visible_lines,
buffer_get_selection = cursorless.buffer_get_selection,
buffer_get_selection_text = cursorless.buffer_get_selection_text,
select_range = cursorless.select_range,
}
return M

View File

@ -0,0 +1,69 @@
local M = {}
-- :lua print(require('cursorless.utils').is_platform_windows())
function M.is_platform_windows()
return vim.uv.os_uname().version:find("Windows")
end
-- :lua print(require('cursorless.utils').is_platform_macos())
function M.is_platform_macos()
return vim.uv.os_uname().version:find("Darwin")
end
-- :lua print(require('cursorless.utils').get_path_separator())
function M.get_path_separator()
if M.is_platform_windows() then
return "\\"
end
return "/"
end
-- https://www.reddit.com/r/neovim/comments/tk1hby/get_the_path_to_the_current_lua_script_in_neovim/
-- https://pgl.yoyo.org/luai/i/debug.getinfo
-- https://www.gammon.com.au/scripts/doc.php?lua=debug.getinfo
-- e.g. :lua print(require('cursorless.utils').cursorless_nvim_path())
-- outputs: C:\Users\User\AppData\Local\nvim-data\lazy\talon.nvim
-- NOTE: Development cursorless-neovim is installed in: C:\Users\User\AppData\Local\nvim\rplugin\node\cursorless-neovim
function M.cursorless_nvim_path()
--source_file=@C:/Users/User/AppData/Local/nvim-data/lazy/talon.nvim/lua/talon/utils.lua
local str = debug.getinfo(1, "S").source
-- print(('source_file=%s'):format(str))
-- skip as the file name is prefixed by "@"
str = str:sub(2)
-- print(('source_file2=%s'):format(str))
if M.is_platform_windows() then
str = str:gsub("/", "\\")
-- print('is_platform_windows')
end
-- print(('source_file3=%s'):format(str))
-- remove where our current file is located to get talon.nvim base path
str = str:sub(0, -1 - #"lua/cursorless/utils.lua")
-- print(('talon.nvim=%s'):format(str))
return str
end
-- assumes we are in terminal mode and switch to normal terminal mode
-- https://www.reddit.com/r/neovim/comments/uk3xmq/change_mode_in_lua/
-- https://neovim.io/doc/user/api.html#nvim_feedkeys()
-- https://neovim.io/doc/user/builtin.html#feedkeys()
-- https://neovim.io/doc/user/api.html#nvim_replace_termcodes()
-- e.g. run in command mode :tmap <c-a> <Cmd>lua mode_switch_nt()<Cr>
function M.mode_switch_nt()
local key = vim.api.nvim_replace_termcodes("<c-\\><c-n>", true, false, true)
vim.api.nvim_feedkeys(key, "n", false)
end
-- assumes we are in normal terminal mode and switch to terminal mode
-- e.g. run in command mode :nmap <c-b> <Cmd>lua mode_switch_t()<Cr>
function M.mode_switch_t()
vim.api.nvim_feedkeys("i", "n", true)
end
-- paste what is in the clipboard
-- https://www.baeldung.com/linux/vim-paste-text
-- e.g. run in command mode :imap <c-b> <Cmd>lua require('cursorless.utils').paste()<Cr>
function M.paste()
vim.cmd([[silent! normal! "+p]])
end
return M

View File

@ -0,0 +1,3 @@
# Command server
This directory contains a minified version of a `command-server`, which enables us to communicate with node from Talon. It is based on [command-server we use for VSCode](https://github.com/pokey/command-server/), but modified to run in a Neovim node subprocess. It can be built by following the instructions from https://github.com/hands-free-vim/command-server/tree/neovim#build

View File

@ -0,0 +1,327 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if ((from && typeof from === "object") || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, {
get: () => from[key],
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable,
});
}
return to;
};
var __toCommonJS = (mod) =>
__copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
default: () => entry,
});
module.exports = __toCommonJS(src_exports);
// src/nativeIo.ts
var import_fs = require("fs");
var import_path2 = require("path");
var import_constants = require("constants");
// src/paths.ts
var import_os = require("os");
var import_path = require("path");
function getCommunicationDirPath() {
const info = (0, import_os.userInfo)();
const suffix = info.uid >= 0 ? `-${info.uid}` : "";
return (0, import_path.join)(
(0, import_os.tmpdir)(),
`neovim-command-server${suffix}`,
);
}
function getSignalDirPath() {
return (0, import_path.join)(getCommunicationDirPath(), "signals");
}
function getRequestPath() {
return (0, import_path.join)(getCommunicationDirPath(), "request.json");
}
function getResponsePath() {
return (0, import_path.join)(getCommunicationDirPath(), "response.json");
}
// src/nativeIo.ts
var import_os2 = require("os");
var import_promises = require("fs/promises");
// src/constants.ts
var NEOVIM_COMMAND_TIMEOUT_MS = 3e3;
// src/nativeIo.ts
var InboundSignal = class {
constructor(path) {
this.path = path;
}
/**
* Gets the current version of the signal. This version string changes every
* time the signal is emitted, and can be used to detect whether signal has
* been emitted between two timepoints.
* @returns The current signal version or null if the signal file could not be
* found
*/
async getVersion() {
try {
return (await (0, import_promises.stat)(this.path)).mtimeMs.toString();
} catch (err) {
if (err.code !== "ENOENT") {
throw err;
}
return null;
}
}
};
var NativeIo = class {
constructor() {
this.responseFile = null;
}
async initialize() {
const communicationDirPath = getCommunicationDirPath();
console.log(`Creating communication dir ${communicationDirPath}`);
(0, import_fs.mkdirSync)(communicationDirPath, {
recursive: true,
mode: 504,
});
const stats = (0, import_fs.lstatSync)(communicationDirPath);
const info = (0, import_os2.userInfo)();
if (
!stats.isDirectory() ||
stats.isSymbolicLink() ||
stats.mode & import_constants.S_IWOTH || // On Windows, uid < 0, so we don't worry about it for simplicity
(info.uid >= 0 && stats.uid !== info.uid)
) {
throw new Error(
`Refusing to proceed because of invalid communication dir ${communicationDirPath}`,
);
}
}
async prepareResponse() {
if (this.responseFile) {
throw new Error("response is already locked");
}
this.responseFile = await (0, import_promises.open)(
getResponsePath(),
"wx",
);
}
async closeResponse() {
if (!this.responseFile) {
throw new Error("response is not locked");
}
await this.responseFile.close();
this.responseFile = null;
}
/**
* Reads the JSON-encoded request from the request file, unlinking the file
* after reading.
* @returns A promise that resolves to a Response object
*/
async readRequest() {
const requestPath = getRequestPath();
const stats = await (0, import_promises.stat)(requestPath);
const request = JSON.parse(
await (0, import_promises.readFile)(requestPath, "utf-8"),
);
if (
Math.abs(stats.mtimeMs - /* @__PURE__ */ new Date().getTime()) >
NEOVIM_COMMAND_TIMEOUT_MS
) {
throw new Error(
"Request file is older than timeout; refusing to execute command",
);
}
return request;
}
/**
* Writes the response to the response file as JSON.
* @param file The file to write to
* @param response The response object to JSON-encode and write to disk
*/
async writeResponse(response) {
if (!this.responseFile) {
throw new Error("response is not locked");
}
await this.responseFile.write(`${JSON.stringify(response)}
`);
}
getInboundSignal(name) {
const signalDir = getSignalDirPath();
const path = (0, import_path2.join)(signalDir, name);
return new InboundSignal(path);
}
};
// ../cursorless_fork/packages/neovim-registry/src/NeovimRegistry.ts
var import_node_events = require("node:events");
var NeovimRegistry = class {
constructor() {
this.apis = /* @__PURE__ */ new Map();
this.commands = /* @__PURE__ */ new Map();
this.eventEmitter = new import_node_events.EventEmitter();
}
registerExtensionApi(extensionId, api) {
this.apis.set(extensionId, api);
}
getExtensionApi(extensionId) {
return this.apis.get(extensionId);
}
registerCommand(commandId, callback) {
this.commands.set(commandId, callback);
}
async executeCommand(commandId, ...rest) {
return await this.commands.get(commandId)(...rest);
}
onEvent(eventName, listener) {
return this.eventEmitter.on(eventName, listener);
}
emitEvent(eventName, ...args) {
return this.eventEmitter.emit(eventName, ...args);
}
};
// ../cursorless_fork/packages/neovim-registry/src/index.ts
function getNeovimRegistry() {
if (global._neovimRegistry == null) {
global._neovimRegistry = new NeovimRegistry();
}
return global._neovimRegistry;
}
// src/commandRunner.ts
var CommandRunner = class {
constructor(io) {
this.io = io;
this.reloadConfiguration = this.reloadConfiguration.bind(this);
this.runCommand = this.runCommand.bind(this);
this.reloadConfiguration();
}
reloadConfiguration() {}
/**
* Reads a command from the request file and executes it. Writes information
* about command execution to the result of the command to the response file,
* If requested, will wait for command to finish, and can also write command
* output to response file. See also documentation for Request / Response
* types.
*/
async runCommand() {
console.log(
"------------------------------------------------------------------------------",
);
await this.io.prepareResponse();
let request;
try {
request = await this.io.readRequest();
} catch (err) {
await this.io.closeResponse();
throw err;
}
const { commandId, args, uuid, returnCommandOutput, waitForFinish } =
request;
const warnings = [];
try {
if (!commandId.match(this.allowRegex)) {
throw new Error("Command not in allowList");
}
if (this.denyRegex != null && commandId.match(this.denyRegex)) {
throw new Error("Command in denyList");
}
const commandPromise = getNeovimRegistry().executeCommand(
commandId,
...args,
);
let commandReturnValue = null;
if (returnCommandOutput) {
commandReturnValue = await commandPromise;
} else if (waitForFinish) {
await commandPromise;
}
await this.io.writeResponse({
error: null,
uuid,
returnValue: commandReturnValue,
warnings,
});
} catch (err) {
await this.io.writeResponse({
error: err.message,
uuid,
warnings,
});
}
await this.io.closeResponse();
}
};
// src/singletons/commandRunner.singleton.ts
var cmdRunner_;
function injectCommandRunner(cmdRunner) {
cmdRunner_ = cmdRunner;
}
function commandRunner() {
if (cmdRunner_ == null) {
throw Error("Tried to access CommandRunner before it was injected");
}
return cmdRunner_;
}
// src/extension.ts
async function activate() {
const io = new NativeIo();
await io.initialize();
const commandRunner2 = new CommandRunner(io);
let focusedElementType;
injectCommandRunner(commandRunner2);
return {
/**
* The type of the focused element in vscode at the moment of the command being executed.
*/
getFocusedElementType: () => focusedElementType,
/**
* These signals can be used as a form of IPC to indicate that an event has
* occurred.
*/
signals: {
/**
* This signal is emitted by the voice engine to indicate that a phrase has
* just begun execution.
*/
prePhrase: io.getInboundSignal("prePhrase"),
},
};
}
// src/index.ts
function entry(plugin) {
plugin.setOptions({ dev: false });
plugin.registerFunction(
"CommandServerLoadExtension",
async () => await loadExtension(plugin),
{ sync: false },
);
plugin.registerFunction("CommandServerRunCommand", () => runCommand(), {
sync: false,
});
}
async function loadExtension(plugin) {
console.log("loadExtension(command-server): start");
await activate();
console.log("loadExtension(command-server): done");
}
async function runCommand() {
console.log("runCommand(command-server): start");
commandRunner().runCommand();
console.log("runCommand(command-server): done");
}

View File

@ -0,0 +1,33 @@
{
"name": "command-server",
"description": "Exposes a file-based RPC API for running VSCode commands",
"publisher": "saidelike",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/hands-free-vim/command-server"
},
"version": "0.9.1",
"main": "./index/index.cjs",
"private": true,
"devDependencies": {
"@types/glob": "^7.1.3",
"@types/mocha": "8.0.4",
"@types/node": "^18.19.22",
"@types/rimraf": "^3.0.0",
"@types/vscode": "^1.53.0",
"@typescript-eslint/eslint-plugin": "^4.9.0",
"@typescript-eslint/parser": "^4.9.0",
"esbuild": "^0.20.2",
"eslint": "^7.15.0",
"glob": "^7.1.6",
"mocha": "8.1.3",
"typescript": "^4.1.2",
"vscode-test": "^1.4.1",
"neovim": "^5.0.1"
},
"dependencies": {
"minimatch": "^3.0.4",
"rimraf": "^3.0.2"
}
}

View File

@ -0,0 +1,16 @@
-- This file gets linked into plugin/helpers.lua of busted nvim config
-- Functions that are exposed to all tests
function _G.get_selected_text()
local _, ls, cs = unpack(vim.fn.getpos("v"))
local _, le, ce = unpack(vim.fn.getpos("."))
return vim.api.nvim_buf_get_text(0, ls - 1, cs - 1, le - 1, ce, {})
end
function _G.convert_table_entries(tbl, func)
local mapped = {}
for k, v in pairs(tbl) do
mapped[k] = func(v)
end
return mapped
end

View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
if ! [[ "${PWD}" == *"cursorless.nvim" ]]; then
echo "ERROR: This script must be run from inside cursorless.nvim/ directory"
exit 1
fi
test_folder=$(mktemp -d "${TMPDIR-/tmp}"/cursorless-busted-test-XXXXX)
export XDG_CONFIG_HOME="${test_folder}/xdg/config/"
export XDG_STATE_HOME="${test_folder}/xdg/local/state/"
export XDG_DATA_HOME="${test_folder}/xdg/local/share/"
dependency_folder="${XDG_DATA_HOME}/nvim/site/pack/testing/start/"
plugin_folder="${XDG_CONFIG_HOME}/nvim/plugin/"
mkdir -p "${plugin_folder}" "${XDG_STATE_HOME}" "${dependency_folder}"
ln -sf "${PWD}" "${dependency_folder}/cursorless.nvim"
# Link in standalone helper functions we want all tests to be able to call
ln -sf "${PWD}/test/helpers.lua" "${plugin_folder}/helpers.lua"
# shellcheck disable=SC2068
command nvim --cmd 'set loadplugins' -l $@
exit_code=$?
rm -rf "${test_folder}"
exit $exit_code

View File

@ -0,0 +1,79 @@
describe("", function()
local cursorless = require("cursorless.cursorless")
describe("window_get_visible_lines() ->", function()
it("can read one visible line", function()
local pos = vim.api.nvim_win_get_cursor(0)[2]
local line = vim.api.nvim_get_current_line()
local nline = line:sub(0, pos) .. "hello" .. line:sub(pos + 1)
vim.api.nvim_set_current_line(nline)
local visible = cursorless.window_get_visible_lines()
assert(table.concat(visible) == table.concat({ 1, 1 }))
end)
it("can read all lines visible on the window", function()
local maxlines = vim.api.nvim_win_get_height(0)
local lines = {}
for _ = 1, (maxlines + 1) do
table.insert(lines, "hello ")
end
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
local visible = cursorless.window_get_visible_lines()
assert(table.concat(visible) == table.concat({ 1, maxlines }))
end)
end)
describe("select_range() ->", function()
it("selects the specified range", function()
local lines = "hello world"
vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(lines, "\n"))
cursorless.select_range(1, 2, 1, 4)
assert(table.concat(_G.get_selected_text()) == "llo")
end)
end)
describe("buffer_get_selection() ->", function()
it(
"can get the forward selection in a format expected by cursorless",
function()
local lines = "hello world"
vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(lines, "\n"))
cursorless.select_range(1, 2, 1, 4)
assert(
table.concat(
_G.convert_table_entries(
cursorless.buffer_get_selection(),
tostring
),
", "
)
== table.concat(
_G.convert_table_entries({ 1, 3, 1, 5, false }, tostring),
", "
)
)
end
)
it(
"can get the backward selection in a format expected by cursorless",
function()
local lines = "hello world"
vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(lines, "\n"))
cursorless.select_range(1, 4, 1, 2)
assert(
table.concat(
_G.convert_table_entries(
cursorless.buffer_get_selection(),
tostring
),
", "
)
== table.concat(
_G.convert_table_entries({ 1, 3, 1, 5, true }, tostring),
", "
)
)
end
)
end)
end)

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: bring line harp and whale
action: {name: replaceWithTarget}
targets:
- type: list
action:
name: replaceWithTarget
source:
type: list
elements:
- type: primitive
modifiers:
@ -13,7 +14,7 @@ command:
mark: {type: decoratedSymbol, symbolColor: default, character: h}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: w}
- {type: implicit}
destination: {type: implicit}
usePrePhraseSnapshot: false
initialState:
documentContents: |+

View File

@ -1,13 +1,13 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: alternate highlight harp
action:
name: highlight
args: [highlight1]
targets:
- type: primitive
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: h}
highlightId: highlight1
usePrePhraseSnapshot: true
spokenFormError: highlight.highlightId
initialState:

View File

@ -1,15 +1,18 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: bring air after air
action: {name: replaceWithTarget}
targets:
- type: primitive
action:
name: replaceWithTarget
source:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
modifiers:
- {type: position, position: after}
destination:
type: primitive
insertionMode: after
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
usePrePhraseSnapshot: true
initialState:
documentContents: a

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring air and bat and cap
action: {name: replaceWithTarget}
targets:
- type: list
action:
name: replaceWithTarget
source:
type: list
elements:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
@ -12,7 +13,7 @@ command:
mark: {type: decoratedSymbol, symbolColor: default, character: b}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
- {type: implicit}
destination: {type: implicit}
usePrePhraseSnapshot: false
initialState:
documentContents: |+

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring air and bat and cap after drum
action: {name: replaceWithTarget}
targets:
- type: list
action:
name: replaceWithTarget
source:
type: list
elements:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
@ -12,10 +13,12 @@ command:
mark: {type: decoratedSymbol, symbolColor: default, character: b}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
- type: primitive
modifiers:
- {type: position, position: after}
mark: {type: decoratedSymbol, symbolColor: default, character: d}
destination:
type: primitive
insertionMode: after
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: d}
usePrePhraseSnapshot: false
initialState:
documentContents: |

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring air and bat and cap after item each
action: {name: replaceWithTarget}
targets:
- type: list
action:
name: replaceWithTarget
source:
type: list
elements:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
@ -12,12 +13,15 @@ command:
mark: {type: decoratedSymbol, symbolColor: default, character: b}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
- type: primitive
modifiers:
- {type: position, position: after}
- type: containingScope
scopeType: {type: collectionItem}
mark: {type: decoratedSymbol, symbolColor: default, character: e}
destination:
type: primitive
insertionMode: after
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: collectionItem}
mark: {type: decoratedSymbol, symbolColor: default, character: e}
usePrePhraseSnapshot: false
initialState:
documentContents: |

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring air and bat and cap before drum
action: {name: replaceWithTarget}
targets:
- type: list
action:
name: replaceWithTarget
source:
type: list
elements:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
@ -12,10 +13,12 @@ command:
mark: {type: decoratedSymbol, symbolColor: default, character: b}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
- type: primitive
modifiers:
- {type: position, position: before}
mark: {type: decoratedSymbol, symbolColor: default, character: d}
destination:
type: primitive
insertionMode: before
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: d}
usePrePhraseSnapshot: false
initialState:
documentContents: |

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring air and bat and cap before item each
action: {name: replaceWithTarget}
targets:
- type: list
action:
name: replaceWithTarget
source:
type: list
elements:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
@ -12,12 +13,15 @@ command:
mark: {type: decoratedSymbol, symbolColor: default, character: b}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
- type: primitive
modifiers:
- {type: position, position: before}
- type: containingScope
scopeType: {type: collectionItem}
mark: {type: decoratedSymbol, symbolColor: default, character: e}
destination:
type: primitive
insertionMode: before
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: collectionItem}
mark: {type: decoratedSymbol, symbolColor: default, character: e}
usePrePhraseSnapshot: false
initialState:
documentContents: |

View File

@ -1,15 +1,18 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: bring air before air
action: {name: replaceWithTarget}
targets:
- type: primitive
action:
name: replaceWithTarget
source:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
modifiers:
- {type: position, position: before}
destination:
type: primitive
insertionMode: before
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
usePrePhraseSnapshot: true
initialState:
documentContents: a

View File

@ -1,15 +1,20 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: bring air to end of air
action: {name: replaceWithTarget}
targets:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
- type: primitive
modifiers:
- {type: position, position: end}
action:
name: replaceWithTarget
source:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
destination:
type: primitive
insertionMode: to
target:
type: primitive
modifiers:
- {type: endOf}
mark: {type: decoratedSymbol, symbolColor: default, character: a}
usePrePhraseSnapshot: true
initialState:
documentContents: a

View File

@ -1,15 +1,20 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: bring air to start of air
action: {name: replaceWithTarget}
targets:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
- type: primitive
modifiers:
- {type: position, position: start}
action:
name: replaceWithTarget
source:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
destination:
type: primitive
insertionMode: to
target:
type: primitive
modifiers:
- {type: startOf}
mark: {type: decoratedSymbol, symbolColor: default, character: a}
usePrePhraseSnapshot: true
initialState:
documentContents: a

View File

@ -1,18 +1,21 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring arg made after look
action: {name: replaceWithTarget}
targets:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: m}
action:
name: replaceWithTarget
source:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: argumentOrParameter}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: l}
modifiers:
- {type: position, position: after}
mark: {type: decoratedSymbol, symbolColor: default, character: m}
destination:
type: primitive
insertionMode: after
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: l}
usePrePhraseSnapshot: true
initialState:
documentContents: |-

View File

@ -1,18 +1,21 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring arg made after vest
action: {name: replaceWithTarget}
targets:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: m}
action:
name: replaceWithTarget
source:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: argumentOrParameter}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
modifiers:
- {type: position, position: after}
mark: {type: decoratedSymbol, symbolColor: default, character: m}
destination:
type: primitive
insertionMode: after
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
usePrePhraseSnapshot: true
initialState:
documentContents: |-

View File

@ -1,18 +1,21 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring arg whale before look
action: {name: replaceWithTarget}
targets:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: w}
action:
name: replaceWithTarget
source:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: argumentOrParameter}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: l}
modifiers:
- {type: position, position: before}
mark: {type: decoratedSymbol, symbolColor: default, character: w}
destination:
type: primitive
insertionMode: before
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: l}
usePrePhraseSnapshot: true
initialState:
documentContents: |-

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring arg fine and zip
action: {name: replaceWithTarget}
targets:
- type: list
action:
name: replaceWithTarget
source:
type: list
elements:
- type: primitive
modifiers:
@ -13,7 +14,7 @@ command:
mark: {type: decoratedSymbol, symbolColor: default, character: f}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: z}
- {type: implicit}
destination: {type: implicit}
usePrePhraseSnapshot: false
initialState:
documentContents: |-

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring arg odd and zip after just paren
action: {name: replaceWithTarget}
targets:
- type: list
action:
name: replaceWithTarget
source:
type: list
elements:
- type: primitive
modifiers:
@ -13,11 +14,14 @@ command:
mark: {type: decoratedSymbol, symbolColor: default, character: o}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: z}
- type: primitive
modifiers:
- {type: position, position: after}
- {type: toRawSelection}
mark: {type: decoratedSymbol, symbolColor: default, character: (}
destination:
type: primitive
insertionMode: after
target:
type: primitive
modifiers:
- {type: toRawSelection}
mark: {type: decoratedSymbol, symbolColor: default, character: (}
usePrePhraseSnapshot: false
initialState:
documentContents: |-

View File

@ -1,17 +1,21 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: bring fine after line vest
action: {name: replaceWithTarget}
targets:
- type: primitive
action:
name: replaceWithTarget
source:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: f}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
modifiers:
- {type: position, position: after}
- type: containingScope
scopeType: {type: line}
destination:
type: primitive
insertionMode: after
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: line}
mark: {type: decoratedSymbol, symbolColor: default, character: v}
usePrePhraseSnapshot: true
initialState:
documentContents: |

View File

@ -1,17 +1,21 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: bring fine before line vest
action: {name: replaceWithTarget}
targets:
- type: primitive
action:
name: replaceWithTarget
source:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: f}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
modifiers:
- {type: position, position: before}
- type: containingScope
scopeType: {type: line}
destination:
type: primitive
insertionMode: before
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: line}
mark: {type: decoratedSymbol, symbolColor: default, character: v}
usePrePhraseSnapshot: true
initialState:
documentContents: |2

View File

@ -1,18 +1,21 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring item air after cap
action: {name: replaceWithTarget}
targets:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
action:
name: replaceWithTarget
source:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: collectionItem}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
modifiers:
- {type: position, position: after}
mark: {type: decoratedSymbol, symbolColor: default, character: a}
destination:
type: primitive
insertionMode: after
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
usePrePhraseSnapshot: true
initialState:
documentContents: |-

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring line air and bat and cap after drum
action: {name: replaceWithTarget}
targets:
- type: list
action:
name: replaceWithTarget
source:
type: list
elements:
- type: primitive
modifiers:
@ -15,10 +16,12 @@ command:
mark: {type: decoratedSymbol, symbolColor: default, character: b}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
- type: primitive
modifiers:
- {type: position, position: after}
mark: {type: decoratedSymbol, symbolColor: default, character: d}
destination:
type: primitive
insertionMode: after
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: d}
usePrePhraseSnapshot: false
initialState:
documentContents: |

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring line air and bat and cap before drum
action: {name: replaceWithTarget}
targets:
- type: list
action:
name: replaceWithTarget
source:
type: list
elements:
- type: primitive
modifiers:
@ -15,10 +16,12 @@ command:
mark: {type: decoratedSymbol, symbolColor: default, character: b}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
- type: primitive
modifiers:
- {type: position, position: before}
mark: {type: decoratedSymbol, symbolColor: default, character: d}
destination:
type: primitive
insertionMode: before
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: d}
usePrePhraseSnapshot: false
initialState:
documentContents: |

View File

@ -1,12 +1,13 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring vest
action: {name: replaceWithTarget}
targets:
- type: primitive
action:
name: replaceWithTarget
source:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
- {type: implicit}
destination: {type: implicit}
usePrePhraseSnapshot: false
initialState:
documentContents: |

View File

@ -1,13 +1,18 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: bring vest to cap
action: {name: replaceWithTarget}
targets:
- type: primitive
action:
name: replaceWithTarget
source:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
destination:
type: primitive
insertionMode: to
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
usePrePhraseSnapshot: false
initialState:
documentContents: |

View File

@ -1,12 +1,13 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: call fine
action: {name: callAsFunction}
targets:
- type: primitive
action:
name: callAsFunction
callee:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: f}
- {type: implicit}
argument: {type: implicit}
usePrePhraseSnapshot: false
initialState:
documentContents: |-

View File

@ -1,12 +1,13 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: call vest
action: {name: callAsFunction}
targets:
- type: primitive
action:
name: callAsFunction
callee:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
- {type: implicit}
argument: {type: implicit}
usePrePhraseSnapshot: false
initialState:
documentContents: |

View File

@ -1,12 +1,14 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: call vest on cap
action: {name: callAsFunction}
targets:
- type: primitive
action:
name: callAsFunction
callee:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
- type: primitive
argument:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: carve vest
action: {name: cutToClipboard}
targets:
- type: primitive
action:
name: cutToClipboard
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: chuck arg made and air
action: {name: remove}
targets:
- type: list
action:
name: remove
target:
type: list
elements:
- type: primitive
modifiers:

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: chuck arg made and air and just sun
action: {name: remove}
targets:
- type: list
action:
name: remove
target:
type: list
elements:
- type: primitive
modifiers:

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: chuck every arg made
action: {name: remove}
targets:
- type: primitive
action:
name: remove
target:
type: primitive
modifiers:
- type: everyScope
scopeType: {type: argumentOrParameter}

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: chuck vest
action: {name: remove}
targets:
- type: primitive
action:
name: remove
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: change vest
action: {name: clearAndSetSelection}
targets:
- type: primitive
action:
name: clearAndSetSelection
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: clone arg
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: argumentOrParameter}

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: clone arg
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: argumentOrParameter}

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: clone every arg
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
modifiers:
- type: everyScope
scopeType: {type: argumentOrParameter}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone harp
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: h}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone harp
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: h}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone token
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: token}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone token
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: token}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone token
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: token}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone token
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: token}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone token
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: token}

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: clone up arg
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: argumentOrParameter}

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: clone up arg
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: argumentOrParameter}

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: clone up every arg
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
modifiers:
- type: everyScope
scopeType: {type: argumentOrParameter}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone up harp
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: h}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone up harp
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: h}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone up token
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: token}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone up token
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: token}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone up token
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: token}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone up token
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: token}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone up token
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: token}

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone up vest
action: {name: insertCopyBefore}
targets:
- type: primitive
action:
name: insertCopyBefore
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,10 +1,11 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: clone vest
action: {name: insertCopyAfter}
targets:
- type: primitive
action:
name: insertCopyAfter
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: comment vest
action: {name: toggleLineComment}
targets:
- type: primitive
action:
name: toggleLineComment
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
usePrePhraseSnapshot: false
initialState:

View File

@ -0,0 +1,37 @@
languageId: plaintext
command:
version: 6
spokenForm: copy second token
action:
name: copyToClipboard
target:
type: primitive
modifiers:
- type: ordinalScope
scopeType: {type: token}
start: 1
length: 1
usePrePhraseSnapshot: false
initialState:
documentContents: |
const value = "Hello world";
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks: {}
finalState:
documentContents: |
const value = "Hello world";
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
clipboard: value
thatMark:
- type: TokenTarget
contentRange:
start: {line: 1, character: 6}
end: {line: 1, character: 11}
isReversed: false
hasExplicitRange: true

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: copy vest
action: {name: copyToClipboard}
targets:
- type: primitive
action:
name: copyToClipboard
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: v}
usePrePhraseSnapshot: false
initialState:

View File

@ -1,12 +1,13 @@
languageId: plaintext
command:
version: 5
version: 6
spokenForm: curly repack round
action:
name: rewrapWithPairedDelimiter
args: ['{', '}']
targets:
- type: primitive
left: "{"
right: "}"
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: surroundingPair, delimiter: parentheses}

View File

@ -1,14 +1,13 @@
languageId: markdown
command:
version: 5
version: 6
spokenForm: custom harp
action:
name: executeCommand
args:
- editor.action.addCommentLine
- {}
targets:
- type: primitive
commandId: editor.action.addCommentLine
options: {}
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: h}
usePrePhraseSnapshot: false
spokenFormError: Action 'executeCommand'

View File

@ -1,10 +1,11 @@
languageId: typescript
command:
version: 5
version: 6
spokenForm: carve every arg made
action: {name: cutToClipboard}
targets:
- type: primitive
action:
name: cutToClipboard
target:
type: primitive
modifiers:
- type: everyScope
scopeType: {type: argumentOrParameter}

View File

@ -0,0 +1,30 @@
languageId: plaintext
command:
version: 7
spokenForm: dedent line
action:
name: outdentLine
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: line}
usePrePhraseSnapshot: true
initialState:
documentContents: " foo"
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
marks: {}
finalState:
documentContents: foo
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
thatMark:
- type: UntypedTarget
contentRange:
start: {line: 0, character: 0}
end: {line: 0, character: 3}
isReversed: false
hasExplicitRange: true

Some files were not shown because too many files have changed in this diff Show More