Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jetbrains cursorless support #2695

Draft
wants to merge 29 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e1e4519
quickjs build - take 1
asoee Nov 13, 2024
5f53332
wip
asoee Nov 13, 2024
bec1c52
add more jetbrains skeleton
asoee Nov 13, 2024
471c428
wip
asoee Nov 16, 2024
70fc653
wip
asoee Nov 18, 2024
f15e88f
first working hats with javet
asoee Nov 23, 2024
d2f1bf9
add setSelection callback
asoee Nov 27, 2024
e325e62
add missing files
asoee Nov 27, 2024
1a935a6
fix jetbrains texteditor visible range
asoee Nov 29, 2024
eea280c
fix active editor state
asoee Nov 30, 2024
23caf2d
add clipboard support
asoee Dec 4, 2024
9bedadd
remove editor state on close
asoee Dec 5, 2024
f16e595
remove debug logging
asoee Dec 5, 2024
b63c8c3
add most of range IDE commands
asoee Dec 7, 2024
203e64d
add reveal range
asoee Dec 7, 2024
52315e2
add jetbrains treesitter support
asoee Dec 9, 2024
9b162f9
Merge branch 'cursorless-dev:main' into cursorless-jetbrains
asoee Dec 10, 2024
1798e43
Remove neovim references from jetbrains package
asoee Dec 10, 2024
863103d
update pnpm-lock.yaml
asoee Dec 10, 2024
8d35372
fix various linting issues
asoee Dec 10, 2024
5a8e4a3
fix more linting issues, and missing lodash dependency
asoee Dec 10, 2024
e39d5b8
dont break build
asoee Dec 10, 2024
cd4c30a
add path shim for cross os path parsing
asoee Dec 13, 2024
d10638a
fix windows was path building
asoee Dec 13, 2024
f6085ba
add missing pathjoin file
asoee Dec 15, 2024
31c678b
support editor visibility
asoee Dec 16, 2024
bdf7fdc
implement flashranges for jetbrains
asoee Dec 18, 2024
d8bbe84
add support for prePhraseVersion
asoee Dec 18, 2024
97218fa
enable multiple hat styles
asoee Dec 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/cursorless-jetbrains/TERMINOLOGY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# TextEditor/TextDocument vs Project/Editor

1. Each Cursorless "TextDocument" corresponds to a Jetbrains "Editor" tab
2. There is no specific handling of projects
43 changes: 43 additions & 0 deletions packages/cursorless-jetbrains/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@cursorless/cursorless-jetbrains",
"version": "1.0.0",
"description": "cursorless in jetbrains",
"main": "./out/cursorless.js",
"private": true,
"type": "module",
"scripts": {
"build": "pnpm run esbuild:prod && pnpm run populate-dist",
"compile": "tsc --build",
"esbuild:base": "esbuild ./src/index.ts --format=esm --target=es2020 --conditions=cursorless:bundler --bundle --main-fields=main,module --outfile=./out/cursorless.js --platform=neutral --external:std --external:fs --external:path",
"esbuild": "pnpm run esbuild:base --sourcemap",
"esbuild:prod": "pnpm run esbuild:base --minify",
"populate-dist": "bash ./scripts/populate-dist.sh",
"populate-dist:prod": "bash ./scripts/populate-dist.sh",
"watch:tsc": "pnpm compile --watch",
"watch:esbuild": "pnpm esbuild --watch",
"watch": "pnpm run --filter @cursorless/cursorless-jetbrains --parallel '/^watch:.*/'",
"clean": "rm -rf ./out tsconfig.tsbuildinfo ./dist ./build"
},
"keywords": [],
"author": "",
"license": "MIT",
"types": "./out/index.d.ts",
"exports": {
".": {
"cursorless:bundler": "./src/index.ts",
"default": "./out/index.cjs"
}
},
"dependencies": {
"@cursorless/common": "workspace:*",
"@cursorless/cursorless-engine": "workspace:*",
"@cursorless/test-case-recorder": "workspace:*",
"lodash-es": "^4.17.21",
"vscode-uri": "^3.0.8",
"web-tree-sitter": "0.24.4"
},
"devDependencies": {
"@types/chai": "^5.0.0",
"@types/lodash-es": "4.17.12"
}
}
5 changes: 5 additions & 0 deletions packages/cursorless-jetbrains/scripts/buildLocal.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

npm run compile && npm run esbuild

cp out/cursorless.js ../../../cursorless-jetbrains/src/main/resources/cursorless/
6 changes: 6 additions & 0 deletions packages/cursorless-jetbrains/scripts/populate-dist.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail

echo "Populating dist directory..."

echo "Nothing to do yet..."
34 changes: 34 additions & 0 deletions packages/cursorless-jetbrains/src/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { CursorlessEngine } from "@cursorless/cursorless-engine";
import { createCursorlessEngine } from "@cursorless/cursorless-engine";
import type { JetbrainsPlugin } from "./ide/JetbrainsPlugin";
import Parser from "web-tree-sitter";
import { JetbrainsTreeSitter } from "./ide/JetbrainsTreeSitter";
import { JetbrainsTreeSitterQueryProvider } from "./ide/JetbrainsTreeSitterQueryProvider";
import { pathJoin } from "./ide/pathJoin";
import { JetbrainsCommandServer } from "./ide/JetbrainsCommandServer";

export async function activate(
plugin: JetbrainsPlugin,
wasmDirectory: string,
): Promise<CursorlessEngine> {
console.log("activate started");
await Parser.init({
locateFile(scriptName: string, _scriptDirectory: string) {
const fullPath = pathJoin(wasmDirectory, scriptName);
return fullPath;
},
});
console.log("Parser initialized");

const commandServerApi = new JetbrainsCommandServer(plugin.client);
const queryProvider = new JetbrainsTreeSitterQueryProvider(plugin.ide);
const engine = await createCursorlessEngine({
ide: plugin.ide,
hats: plugin.hats,
treeSitterQueryProvider: queryProvider,
treeSitter: new JetbrainsTreeSitter(wasmDirectory),
commandServerApi: commandServerApi,
});
console.log("activate completed");
return engine;
}
25 changes: 25 additions & 0 deletions packages/cursorless-jetbrains/src/ide/JetbrainsCapabilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Capabilities, CommandCapabilityMap } from "@cursorless/common";

const COMMAND_CAPABILITIES: CommandCapabilityMap = {
clipboardCopy: { acceptsLocation: true },
clipboardPaste: true,
toggleLineComment: { acceptsLocation: true },
indentLine: { acceptsLocation: true },
outdentLine: { acceptsLocation: true },
rename: { acceptsLocation: true },
quickFix: { acceptsLocation: true },
revealDefinition: { acceptsLocation: true },
revealTypeDefinition: { acceptsLocation: true },
showHover: undefined,
showDebugHover: undefined,
extractVariable: { acceptsLocation: true },
fold: { acceptsLocation: true },
highlight: { acceptsLocation: true },
unfold: { acceptsLocation: true },
showReferences: { acceptsLocation: true },
insertLineAfter: { acceptsLocation: true },
};

export class JetbrainsCapabilities implements Capabilities {
commands = COMMAND_CAPABILITIES;
}
14 changes: 14 additions & 0 deletions packages/cursorless-jetbrains/src/ide/JetbrainsClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface JetbrainsClient {
prePhraseVersion(): string | PromiseLike<string | null> | null;
clipboardCopy(editorId: string, rangesJson: string): void;
clipboardPaste(editorId: string): void;
hatsUpdated(hatsJson: string): void;
documentUpdated(editorId: string, updateJson: string): void;
setSelection(editorId: string, selectionJson: string): void;
executeCommand(editorId: string, command: string, jsonArgs: string): string;
executeRangeCommand(editorId: string, commandJson: string): string;
insertLineAfter(editorId: string, rangesJson: string): void;
revealLine(editorId: string, line: number, revealAt: string): void;
readQuery(filename: string): string | undefined;
flashRanges(flashRangesJson: string): string | undefined;
}
23 changes: 23 additions & 0 deletions packages/cursorless-jetbrains/src/ide/JetbrainsClipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Clipboard, Range } from "@cursorless/common";
import type { JetbrainsClient } from "./JetbrainsClient";

export class JetbrainsClipboard implements Clipboard {
constructor(private client: JetbrainsClient) {}

async readText(): Promise<string> {
return "";
}

async writeText(_value: string): Promise<void> {
return;
}

async copy(editorId: string, ranges: Range[]): Promise<void> {
const rangesJson = JSON.stringify(ranges);
this.client.clipboardCopy(editorId, rangesJson);
}

async paste(editorId: string): Promise<void> {
this.client.clipboardPaste(editorId);
}
}
25 changes: 25 additions & 0 deletions packages/cursorless-jetbrains/src/ide/JetbrainsCommandServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { JetbrainsClient } from "./JetbrainsClient";
import type {
CommandServerApi,
FocusedElementType,
InboundSignal,
} from "@cursorless/common";

export class JetbrainsCommandServer implements CommandServerApi {
private client!: JetbrainsClient;
readonly signals: { prePhrase: InboundSignal } = {
prePhrase: {
getVersion: async () => {
return this.client.prePhraseVersion();
},
},
};

constructor(client: JetbrainsClient) {
this.client = client;
}

getFocusedElementType(): Promise<FocusedElementType | undefined> {
return Promise.resolve(undefined);
}
}
42 changes: 42 additions & 0 deletions packages/cursorless-jetbrains/src/ide/JetbrainsConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type {
Configuration,
ConfigurationScope,
CursorlessConfiguration,
} from "@cursorless/common";
import { CONFIGURATION_DEFAULTS } from "@cursorless/common";
import type { GetFieldType, Paths } from "@cursorless/common";
import { Notifier } from "@cursorless/common";
import { get } from "lodash-es";

export class JetbrainsConfiguration implements Configuration {
private notifier = new Notifier();
private configuration: CursorlessConfiguration = CONFIGURATION_DEFAULTS;

constructor(configuration: CursorlessConfiguration) {
this.configuration = configuration;
}

getOwnConfiguration<Path extends Paths<CursorlessConfiguration>>(
path: Path,
_scope?: ConfigurationScope,
): GetFieldType<CursorlessConfiguration, Path> {
return get(this.configuration, path) as GetFieldType<
CursorlessConfiguration,
Path
>;
}

onDidChangeConfiguration = this.notifier.registerListener;

updateConfiguration(configuration: CursorlessConfiguration) {
this.configuration = configuration;
this.notifier.notifyListeners();
}
}

export function createJetbrainsConfiguration(
configuration: CursorlessConfiguration,
): JetbrainsConfiguration {
return new JetbrainsConfiguration(configuration);
}

Loading