Skip to content

Commit ecaf27f

Browse files
committed
PR feedback
1 parent 38d35f7 commit ecaf27f

File tree

7 files changed

+124
-88
lines changed

7 files changed

+124
-88
lines changed

packages/cursorless-engine/src/cursorlessEngine.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { MarkStageFactoryImpl } from "./processTargets/MarkStageFactoryImpl";
99
import { ModifierStageFactoryImpl } from "./processTargets/ModifierStageFactoryImpl";
1010
import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandlers";
1111
import { injectIde } from "./singletons/ide.singleton";
12-
import { LanguageDefinitionsImpl } from "./languages/LanguageDefinitionsImpl";
12+
import { LanguageDefinitions } from "./languages/LanguageDefinitions";
1313

1414
export function createCursorlessEngine(
1515
treeSitter: TreeSitter,
@@ -37,7 +37,7 @@ export function createCursorlessEngine(
3737

3838
const testCaseRecorder = new TestCaseRecorder(hatTokenMap);
3939

40-
const languageDefinitions = new LanguageDefinitionsImpl(treeSitter);
40+
const languageDefinitions = new LanguageDefinitions(treeSitter);
4141
const scopeHandlerFactory = new ScopeHandlerFactoryImpl(languageDefinitions);
4242
const markStageFactory = new MarkStageFactoryImpl();
4343
const modifierStageFactory = new ModifierStageFactoryImpl(
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { ScopeType, SimpleScopeType } from "@cursorless/common";
2+
import { Query } from "web-tree-sitter";
3+
import { ide } from "../singletons/ide.singleton";
4+
import { join } from "path";
5+
import { TreeSitterScopeHandler } from "../processTargets/modifiers/scopeHandlers";
6+
import { TreeSitter } from "../typings/TreeSitter";
7+
import { existsSync, readFileSync } from "fs";
8+
import { LanguageId } from "./constants";
9+
10+
/**
11+
* Represents a language definition for a single language, including the
12+
* tree-sitter query used to extract scopes for the given language
13+
*/
14+
export class LanguageDefinition {
15+
private constructor(
16+
private treeSitter: TreeSitter,
17+
/**
18+
* The tree-sitter query used to extract scopes for the given language.
19+
* Note that this query contains patterns for all scope types that the
20+
* language supports using new-style tree-sitter queries
21+
*/
22+
private query: Query,
23+
) {}
24+
25+
/**
26+
* Construct a language definition for the given language id, if the language
27+
* has a new-style query definition, or return undefined if the language doesn't
28+
*
29+
* @param treeSitter The tree-sitter instance to use for parsing
30+
* @param languageId The language id for which to create a language definition
31+
* @returns A language definition for the given language id, or undefined if the given language
32+
* id doesn't have a new-style query definition
33+
*/
34+
static create(
35+
treeSitter: TreeSitter,
36+
languageId: LanguageId,
37+
): LanguageDefinition | undefined {
38+
const queryPath = join(ide().assetsRoot, "queries", `${languageId}.scm`);
39+
40+
if (!existsSync(queryPath)) {
41+
return undefined;
42+
}
43+
44+
const rawLanguageQueryString = readFileSync(queryPath, "utf8");
45+
46+
return new LanguageDefinition(
47+
treeSitter,
48+
treeSitter.getLanguage(languageId)!.query(rawLanguageQueryString),
49+
);
50+
}
51+
52+
/**
53+
* @param scopeType The scope type for which to get a scope handler
54+
* @returns A scope handler for the given scope type and language id, or
55+
* undefined if the given scope type / language id combination is still using
56+
* legacy pathways
57+
*/
58+
getScopeHandler(scopeType: ScopeType) {
59+
if (!this.query.captureNames.includes(scopeType.type)) {
60+
return undefined;
61+
}
62+
63+
return new TreeSitterScopeHandler(
64+
this.treeSitter,
65+
this.query,
66+
scopeType as SimpleScopeType,
67+
);
68+
}
69+
}

packages/cursorless-engine/src/languages/LanguageDefinitionImpl.ts

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,54 @@
1-
import { ScopeType } from "@cursorless/common";
2-
import { ScopeHandler } from "../processTargets/modifiers/scopeHandlers/scopeHandler.types";
1+
import { TreeSitter } from "..";
2+
import { LanguageDefinition } from "./LanguageDefinition";
3+
import { LanguageId } from "./constants";
34

4-
export interface LanguageDefinitions {
5-
get(languageId: string): LanguageDefinition | undefined;
6-
}
5+
/**
6+
* Sentinel value to indicate that a language doesn't have
7+
* a new-style query definition file
8+
*/
9+
const LANGUAGE_UNDEFINED = Symbol("LANGUAGE_UNDEFINED");
10+
11+
/**
12+
* Keeps a map from language ids to {@link LanguageDefinition} instances,
13+
* constructing them as necessary
14+
*/
15+
export class LanguageDefinitions {
16+
/**
17+
* Maps from language id to {@link LanguageDefinition} or
18+
* {@link LANGUAGE_UNDEFINED} if language doesn't have new-style definitions.
19+
* We use a sentinel value instead of undefined so that we can distinguish
20+
* between a situation where we haven't yet checked whether a language has a
21+
* new-style query definition and a situation where we've checked and found
22+
* that it doesn't. The former case is represented by `undefined` (due to the
23+
* semantics of {@link Map.get}), while the latter is represented by the
24+
* sentinel value.
25+
*/
26+
private languageDefinitions: Map<
27+
string,
28+
LanguageDefinition | typeof LANGUAGE_UNDEFINED
29+
> = new Map();
30+
31+
constructor(private treeSitter: TreeSitter) {}
32+
33+
/**
34+
* Get a language definition for the given language id, if the language
35+
* has a new-style query definition, or return undefined if the language doesn't
36+
*
37+
* @param languageId The language id for which to get a language definition
38+
* @returns A language definition for the given language id, or undefined if
39+
* the given language id doesn't have a new-style query definition
40+
*/
41+
get(languageId: string): LanguageDefinition | undefined {
42+
let definition = this.languageDefinitions.get(languageId);
43+
44+
if (definition == null) {
45+
definition =
46+
LanguageDefinition.create(this.treeSitter, languageId as LanguageId) ??
47+
LANGUAGE_UNDEFINED;
48+
49+
this.languageDefinitions.set(languageId, definition);
50+
}
751

8-
export interface LanguageDefinition {
9-
maybeGetLanguageScopeHandler: (
10-
scopeType: ScopeType,
11-
) => ScopeHandler | undefined;
52+
return definition === LANGUAGE_UNDEFINED ? undefined : definition;
53+
}
1254
}

packages/cursorless-engine/src/languages/LanguageDefinitionsImpl.ts

Lines changed: 0 additions & 37 deletions
This file was deleted.

packages/cursorless-engine/src/languages/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import { SupportedLanguageId, supportedLanguageIds } from "./constants";
33
export function isLanguageSupported(
44
languageId: string,
55
): languageId is SupportedLanguageId {
6-
return languageId in supportedLanguageIds;
6+
return supportedLanguageIds.includes(languageId as SupportedLanguageId);
77
}

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory {
5656
default:
5757
return this.languageDefinitions
5858
.get(languageId)
59-
?.maybeGetLanguageScopeHandler(scopeType);
59+
?.getScopeHandler(scopeType);
6060
}
6161
}
6262
}

0 commit comments

Comments
 (0)