@@ -32,6 +32,7 @@ import {
32
32
Completions ,
33
33
computePositionOfLineAndCharacter ,
34
34
computeSuggestionDiagnostics ,
35
+ containsParseError ,
35
36
createDocumentRegistry ,
36
37
createGetCanonicalFileName ,
37
38
createMultiMap ,
@@ -69,6 +70,7 @@ import {
69
70
filter ,
70
71
find ,
71
72
FindAllReferences ,
73
+ findAncestor ,
72
74
findChildOfKind ,
73
75
findPrecedingToken ,
74
76
first ,
@@ -196,6 +198,7 @@ import {
196
198
length ,
197
199
LineAndCharacter ,
198
200
lineBreakPart ,
201
+ LinkedEditingInfo ,
199
202
LiteralType ,
200
203
map ,
201
204
mapDefined ,
@@ -2480,6 +2483,57 @@ export function createLanguageService(
2480
2483
}
2481
2484
}
2482
2485
2486
+ function getLinkedEditingRangeAtPosition ( fileName : string , position : number ) : LinkedEditingInfo | undefined {
2487
+ const sourceFile = syntaxTreeCache . getCurrentSourceFile ( fileName ) ;
2488
+ const token = findPrecedingToken ( position , sourceFile ) ;
2489
+ if ( ! token || token . parent . kind === SyntaxKind . SourceFile ) return undefined ;
2490
+
2491
+ if ( isJsxFragment ( token . parent . parent ) ) {
2492
+ const openFragment = token . parent . parent . openingFragment ;
2493
+ const closeFragment = token . parent . parent . closingFragment ;
2494
+ if ( containsParseError ( openFragment ) || containsParseError ( closeFragment ) ) return undefined ;
2495
+
2496
+ const openPos = openFragment . getStart ( sourceFile ) + 1 ; // "<".length
2497
+ const closePos = closeFragment . getStart ( sourceFile ) + 2 ; // "</".length
2498
+
2499
+ // only allows linked editing right after opening bracket: <| ></| >
2500
+ if ( ( position !== openPos ) && ( position !== closePos ) ) return undefined ;
2501
+
2502
+ return { ranges : [ { start : openPos , length : 0 } , { start : closePos , length : 0 } ] } ;
2503
+ }
2504
+ else {
2505
+ // determines if the cursor is in an element tag
2506
+ const tag = findAncestor ( token . parent ,
2507
+ n => {
2508
+ if ( isJsxOpeningElement ( n ) || isJsxClosingElement ( n ) ) {
2509
+ return true ;
2510
+ }
2511
+ return false ;
2512
+ } ) ;
2513
+ if ( ! tag ) return undefined ;
2514
+ Debug . assert ( isJsxOpeningElement ( tag ) || isJsxClosingElement ( tag ) , "tag should be opening or closing element" ) ;
2515
+
2516
+ const openTag = tag . parent . openingElement ;
2517
+ const closeTag = tag . parent . closingElement ;
2518
+
2519
+ const openTagStart = openTag . tagName . getStart ( sourceFile ) ;
2520
+ const openTagEnd = openTag . tagName . end ;
2521
+ const closeTagStart = closeTag . tagName . getStart ( sourceFile ) ;
2522
+ const closeTagEnd = closeTag . tagName . end ;
2523
+
2524
+ // only return linked cursors if the cursor is within a tag name
2525
+ if ( ! ( openTagStart <= position && position <= openTagEnd || closeTagStart <= position && position <= closeTagEnd ) ) return undefined ;
2526
+
2527
+ // only return linked cursors if text in both tags is identical
2528
+ const openingTagText = openTag . tagName . getText ( sourceFile ) ;
2529
+ if ( openingTagText !== closeTag . tagName . getText ( sourceFile ) ) return undefined ;
2530
+
2531
+ return {
2532
+ ranges : [ { start : openTagStart , length : openTagEnd - openTagStart } , { start : closeTagStart , length : closeTagEnd - closeTagStart } ] ,
2533
+ } ;
2534
+ }
2535
+ }
2536
+
2483
2537
function getLinesForRange ( sourceFile : SourceFile , textRange : TextRange ) {
2484
2538
return {
2485
2539
lineStarts : sourceFile . getLineStarts ( ) ,
@@ -3011,6 +3065,7 @@ export function createLanguageService(
3011
3065
getDocCommentTemplateAtPosition,
3012
3066
isValidBraceCompletionAtPosition,
3013
3067
getJsxClosingTagAtPosition,
3068
+ getLinkedEditingRangeAtPosition,
3014
3069
getSpanOfEnclosingComment,
3015
3070
getCodeFixesAtPosition,
3016
3071
getCombinedCodeFix,
0 commit comments