-
-
Notifications
You must be signed in to change notification settings - Fork 79
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
Fix JSON surrounding pairs #370
Merged
Merged
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
277303e
Initial working version
pokey e266d0c
Fixed java surrounding pair
pokey 2b0197c
Fix typescript
pokey 05e27d4
Improve angles
pokey c261c8f
Add documentation
pokey 9285874
Typescript template string tweaks
pokey 8764d32
Fix ci
pokey 907d780
Remove unused import
pokey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export const supportedLanguageIds = [ | ||
"c", | ||
"cpp", | ||
"csharp", | ||
"java", | ||
"javascript", | ||
"javascriptreact", | ||
"json", | ||
"jsonc", | ||
"python", | ||
"typescript", | ||
"typescriptreact", | ||
] as const; | ||
|
||
export type SupportedLanguageId = typeof supportedLanguageIds[number]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { SyntaxNode } from "web-tree-sitter"; | ||
import { notSupported } from "../util/nodeMatchers"; | ||
import { selectionWithEditorFromRange } from "../util/selectionUtils"; | ||
import { | ||
NodeMatcher, | ||
NodeMatcherValue, | ||
ScopeType, | ||
SelectionWithEditor, | ||
} from "../typings/Types"; | ||
import cpp from "./cpp"; | ||
import csharp from "./csharp"; | ||
import { patternMatchers as json } from "./json"; | ||
import { patternMatchers as typescript } from "./typescript"; | ||
import { patternMatchers as java } from "./java"; | ||
import python from "./python"; | ||
import { UnsupportedLanguageError } from "../errors"; | ||
import { SupportedLanguageId } from "./constants"; | ||
|
||
export function getNodeMatcher( | ||
languageId: string, | ||
scopeType: ScopeType, | ||
includeSiblings: boolean | ||
): NodeMatcher { | ||
const matchers = languageMatchers[languageId as SupportedLanguageId]; | ||
|
||
if (matchers == null) { | ||
throw new UnsupportedLanguageError(languageId); | ||
} | ||
|
||
const matcher = matchers[scopeType]; | ||
|
||
if (matcher == null) { | ||
return notSupported; | ||
} | ||
|
||
if (includeSiblings) { | ||
return matcherIncludeSiblings(matcher); | ||
} | ||
|
||
return matcher; | ||
} | ||
|
||
const languageMatchers: Record< | ||
SupportedLanguageId, | ||
Record<ScopeType, NodeMatcher> | ||
> = { | ||
c: cpp, | ||
cpp: cpp, | ||
csharp: csharp, | ||
java, | ||
javascript: typescript, | ||
javascriptreact: typescript, | ||
json, | ||
jsonc: json, | ||
python, | ||
typescript, | ||
typescriptreact: typescript, | ||
}; | ||
|
||
function matcherIncludeSiblings(matcher: NodeMatcher): NodeMatcher { | ||
return ( | ||
selection: SelectionWithEditor, | ||
node: SyntaxNode | ||
): NodeMatcherValue[] | null => { | ||
let matches = matcher(selection, node); | ||
if (matches == null) { | ||
return null; | ||
} | ||
matches = matches.flatMap((match) => | ||
iterateNearestIterableAncestor( | ||
match.node, | ||
selectionWithEditorFromRange(selection, match.selection.selection), | ||
matcher | ||
) | ||
) as NodeMatcherValue[]; | ||
if (matches.length > 0) { | ||
return matches; | ||
} | ||
return null; | ||
}; | ||
} | ||
|
||
function iterateNearestIterableAncestor( | ||
node: SyntaxNode, | ||
selection: SelectionWithEditor, | ||
nodeMatcher: NodeMatcher | ||
) { | ||
let parent: SyntaxNode | null = node.parent; | ||
while (parent != null) { | ||
const matches = parent!.namedChildren | ||
.flatMap((sibling) => nodeMatcher(selection, sibling)) | ||
.filter((match) => match != null) as NodeMatcherValue[]; | ||
if (matches.length > 0) { | ||
return matches; | ||
} | ||
parent = parent.parent; | ||
} | ||
return []; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { SyntaxNode } from "web-tree-sitter"; | ||
import { SelectionWithEditor } from "../typings/Types"; | ||
import { stringTextFragmentExtractor as jsonStringTextFragmentExtractor } from "./json"; | ||
import { stringTextFragmentExtractor as javaStringTextFragmentExtractor } from "./java"; | ||
import { stringTextFragmentExtractor as typescriptStringTextFragmentExtractor } from "./typescript"; | ||
import { UnsupportedLanguageError } from "../errors"; | ||
import { Range } from "vscode"; | ||
import { SupportedLanguageId } from "./constants"; | ||
import { | ||
getNodeInternalRange, | ||
getNodeRange, | ||
makeRangeFromPositions, | ||
} from "../util/nodeSelectors"; | ||
import { getNodeMatcher } from "./getNodeMatcher"; | ||
import { notSupported } from "../util/nodeMatchers"; | ||
|
||
export type TextFragmentExtractor = ( | ||
node: SyntaxNode, | ||
selection: SelectionWithEditor | ||
) => Range | null; | ||
|
||
function constructDefaultTextFragmentExtractor( | ||
languageId: SupportedLanguageId, | ||
stringTextFragmentExtractor?: TextFragmentExtractor | ||
): TextFragmentExtractor { | ||
const commentNodeMatcher = getNodeMatcher(languageId, "comment", false); | ||
stringTextFragmentExtractor = | ||
stringTextFragmentExtractor ?? | ||
constructDefaultStringTextFragmentExtractor(languageId); | ||
|
||
return (node: SyntaxNode, selection: SelectionWithEditor) => { | ||
const stringTextFragment = stringTextFragmentExtractor!(node, selection); | ||
|
||
if (stringTextFragment != null) { | ||
return stringTextFragment; | ||
} | ||
|
||
if ( | ||
commentNodeMatcher !== notSupported && | ||
commentNodeMatcher(selection, node) != null | ||
) { | ||
return getNodeRange(node); | ||
} | ||
|
||
// Treat error nodes as raw text so that the surrounding pair matcher can | ||
// still be useful when we have a bad parse tree | ||
if (node.type === "ERROR") { | ||
return getNodeRange(node); | ||
} | ||
|
||
return null; | ||
}; | ||
} | ||
|
||
function constructDefaultStringTextFragmentExtractor( | ||
languageId: SupportedLanguageId | ||
): TextFragmentExtractor { | ||
const stringNodeMatcher = getNodeMatcher(languageId, "string", false); | ||
|
||
return (node: SyntaxNode, selection: SelectionWithEditor) => { | ||
if (stringNodeMatcher(selection, node) != null) { | ||
// Exclude starting and ending quotation marks | ||
return getNodeInternalRange(node); | ||
} | ||
|
||
return null; | ||
}; | ||
} | ||
|
||
/** | ||
* Returns a function which can be used to extract the range of a text fragment | ||
* from within a parsed language. This function should only return a nominal | ||
* range for fragments within the document that should be treated like raw text, | ||
* such as comments strings or error nodes. In these cases we want our | ||
* surrounding pair algorithm to fall back to a pure raw text-based approach. | ||
* @param languageId The language for which to get the text fragment extractor | ||
* for | ||
* @returns The text fragment extractor for the given language | ||
*/ | ||
export default function getTextFragmentExtractor( | ||
languageId: string | ||
): TextFragmentExtractor { | ||
const extractor = textFragmentExtractors[languageId as SupportedLanguageId]; | ||
|
||
if (extractor == null) { | ||
throw new UnsupportedLanguageError(languageId); | ||
} | ||
|
||
return extractor; | ||
} | ||
|
||
const textFragmentExtractors: Record< | ||
SupportedLanguageId, | ||
TextFragmentExtractor | ||
> = { | ||
c: constructDefaultTextFragmentExtractor("c"), | ||
cpp: constructDefaultTextFragmentExtractor("cpp"), | ||
csharp: constructDefaultTextFragmentExtractor("csharp"), | ||
java: constructDefaultTextFragmentExtractor( | ||
"java", | ||
javaStringTextFragmentExtractor | ||
), | ||
javascript: constructDefaultTextFragmentExtractor( | ||
"javascript", | ||
typescriptStringTextFragmentExtractor | ||
), | ||
javascriptreact: constructDefaultTextFragmentExtractor( | ||
"javascriptreact", | ||
typescriptStringTextFragmentExtractor | ||
), | ||
jsonc: constructDefaultTextFragmentExtractor( | ||
"jsonc", | ||
jsonStringTextFragmentExtractor | ||
), | ||
json: constructDefaultTextFragmentExtractor( | ||
"json", | ||
jsonStringTextFragmentExtractor | ||
), | ||
python: constructDefaultTextFragmentExtractor("python"), | ||
typescript: constructDefaultTextFragmentExtractor( | ||
"typescript", | ||
typescriptStringTextFragmentExtractor | ||
), | ||
typescriptreact: constructDefaultTextFragmentExtractor( | ||
"typescriptreact", | ||
typescriptStringTextFragmentExtractor | ||
), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,95 +1,7 @@ | ||
import { SyntaxNode } from "web-tree-sitter"; | ||
import { notSupported } from "../util/nodeMatchers"; | ||
import { selectionWithEditorFromRange } from "../util/selectionUtils"; | ||
import { | ||
NodeMatcher, | ||
NodeMatcherValue, | ||
ScopeType, | ||
SelectionWithEditor, | ||
} from "../typings/Types"; | ||
import cpp from "./cpp"; | ||
import csharp from "./csharp"; | ||
import java from "./java"; | ||
import json from "./json"; | ||
import python from "./python"; | ||
import typescript from "./typescript"; | ||
import { UnsupportedLanguageError } from "../errors"; | ||
import { SupportedLanguageId, supportedLanguageIds } from "./constants"; | ||
|
||
const languageMatchers: Record<string, Record<ScopeType, NodeMatcher>> = { | ||
c: cpp, | ||
cpp: cpp, | ||
csharp: csharp, | ||
java, | ||
javascript: typescript, | ||
javascriptreact: typescript, | ||
json, | ||
jsonc: json, | ||
python, | ||
typescript, | ||
typescriptreact: typescript, | ||
}; | ||
|
||
export function getNodeMatcher( | ||
languageId: string, | ||
scopeType: ScopeType, | ||
includeSiblings: boolean | ||
): NodeMatcher { | ||
const matchers = languageMatchers[languageId]; | ||
|
||
if (matchers == null) { | ||
throw new UnsupportedLanguageError(languageId); | ||
} | ||
|
||
const matcher = matchers[scopeType]; | ||
|
||
if (matcher == null) { | ||
return notSupported; | ||
} | ||
|
||
if (includeSiblings) { | ||
return matcherIncludeSiblings(matcher); | ||
} | ||
|
||
return matcher; | ||
} | ||
|
||
function matcherIncludeSiblings(matcher: NodeMatcher): NodeMatcher { | ||
return ( | ||
selection: SelectionWithEditor, | ||
node: SyntaxNode | ||
): NodeMatcherValue[] | null => { | ||
let matches = matcher(selection, node); | ||
if (matches == null) { | ||
return null; | ||
} | ||
matches = matches.flatMap((match) => | ||
iterateNearestIterableAncestor( | ||
match.node, | ||
selectionWithEditorFromRange(selection, match.selection.selection), | ||
matcher | ||
) | ||
) as NodeMatcherValue[]; | ||
if (matches.length > 0) { | ||
return matches; | ||
} | ||
return null; | ||
}; | ||
} | ||
|
||
function iterateNearestIterableAncestor( | ||
node: SyntaxNode, | ||
selection: SelectionWithEditor, | ||
nodeMatcher: NodeMatcher | ||
) { | ||
let parent: SyntaxNode | null = node.parent; | ||
while (parent != null) { | ||
const matches = parent!.namedChildren | ||
.flatMap((sibling) => nodeMatcher(selection, sibling)) | ||
.filter((match) => match != null) as NodeMatcherValue[]; | ||
if (matches.length > 0) { | ||
return matches; | ||
} | ||
parent = parent.parent; | ||
} | ||
return []; | ||
export function isLanguageSupported( | ||
languageId: string | ||
): languageId is SupportedLanguageId { | ||
return languageId in supportedLanguageIds; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved stuff out of
index.ts