import { Position , Range , CompletionItem , Hover , Diagnostic , ColorPresentation , SymbolInformation , LocationLink , TextDocumentEdit , CodeAction , SelectionRange , TextEdit , InsertReplaceEdit } from 'vscode-languageserver' ; import { TagInformation , offsetAt , positionAt , getLineOffsets } from './utils' ; import { SourceMapConsumer } from 'source-map' ; import { Logger } from '../../logger' ; export interface DocumentMapper { param generatedPosition getOriginalPosition ( generatedPosition ) : Position ; param originalPosition getGeneratedPosition ( originalPosition ) : Position ; param pos isInGenerated ( pos ) : boolean ; /**
     * Get document URL
     */ getURL ( ) : string ; /**
     * Implement this if you need teardown logic before this mapper gets cleaned up.
     */ destroy ? ( ) : void ; } /**
 * Does not map, returns positions as is.
 */ export class IdentityMapper implements DocumentMapper { constructor ( private url , private parent ? ) { } getOriginalPosition ( generatedPosition ) { if ( this . parent ) { generatedPosition = this . getOriginalPosition ( generatedPosition ) ; } return generatedPosition ; } getGeneratedPosition ( originalPosition ) { if ( this . parent ) { originalPosition = this . getGeneratedPosition ( originalPosition ) ; } return originalPosition ; } isInGenerated ( position ) { if ( this . parent && ! this . parent . isInGenerated ( position ) ) { return false ; } return true ; } getURL ( ) { return this . url ; } destroy ( ) { this . parent ?. destroy ?. ( ) ; } } /**
 * Maps positions in a fragment relative to a parent.
 */ export class FragmentMapper implements DocumentMapper { private lineOffsetsOriginal = getLineOffsets ( this . originalText ) ; private lineOffsetsGenerated = getLineOffsets ( this . tagInfo . content ) ; constructor ( private originalText , private tagInfo , private url ) { } getOriginalPosition ( generatedPosition ) { const parentOffset = this . offsetInParent ( offsetAt ( generatedPosition , this . tagInfo . content , this . lineOffsetsGenerated ) ) ; return positionAt ( parentOffset , this . originalText , this . lineOffsetsOriginal ) ; } private offsetInParent ( offset ) { return this . tagInfo . start + offset ; } getGeneratedPosition ( originalPosition ) { const fragmentOffset = offsetAt ( originalPosition , this . originalText , this . lineOffsetsOriginal ) - this . tagInfo . start ; return positionAt ( fragmentOffset , this . tagInfo . content , this . lineOffsetsGenerated ) ; } isInGenerated ( pos ) { const offset = offsetAt ( pos , this . originalText , this . lineOffsetsOriginal ) ; return offset >= this . tagInfo . start && offset <= this . tagInfo . end ; } getURL ( ) { return this . url ; } } export class SourceMapDocumentMapper implements DocumentMapper { constructor ( protected consumer , protected sourceUri , private parent ? ) { } getOriginalPosition ( generatedPosition ) { if ( this . parent ) { generatedPosition = this . parent . getOriginalPosition ( generatedPosition ) ; } if ( generatedPosition . line < 0 ) { return { line : - 1 , character : - 1 } ; } const mapped = this . consumer . originalPositionFor ( { line : generatedPosition . line + 1 , column : generatedPosition . character } ) ; if ( ! mapped ) { return { line : - 1 , character : - 1 } ; } if ( mapped . line === 0 ) { Logger . log ( 'Got 0 mapped line from' , generatedPosition , 'col was' , mapped . column ) ; } return { line : ( mapped . line || 0 ) - 1 , character : mapped . column || 0 } ; } getGeneratedPosition ( originalPosition ) { if ( this . parent ) { originalPosition = this . parent . getGeneratedPosition ( originalPosition ) ; } const mapped = this . consumer . generatedPositionFor ( { line : originalPosition . line + 1 , column : originalPosition . character , source : this . sourceUri } ) ; if ( ! mapped ) { return { line : - 1 , character : - 1 } ; } const result = { line : ( mapped . line || 0 ) - 1 , character : mapped . column || 0 } ; if ( result . line < 0 ) { return result ; } return result ; } isInGenerated ( position ) { if ( this . parent && ! this . isInGenerated ( position ) ) { return false ; } const generated = this . getGeneratedPosition ( position ) ; return generated . line >= 0 ; } getURL ( ) { return this . sourceUri ; } /**
     * Needs to be called when source mapper is no longer needed in order to prevent memory leaks.
     */ destroy ( ) { this . parent ?. destroy ?. ( ) ; this . consumer . destroy ( ) ; } } export function mapRangeToOriginal ( fragment , range ) { const originalRange = { start : fragment . getOriginalPosition ( range . start ) , end : fragment . getOriginalPosition ( range . end ) } ; if ( originalRange . start . line === originalRange . end . line && range . start . line === range . end . line && originalRange . end . character - originalRange . start . character === range . end . character - range . start . character - 1 ) { originalRange . end . character += 1 ; } return originalRange ; } export function mapRangeToGenerated ( fragment , range ) { return Range . create ( fragment . getGeneratedPosition ( range . start ) , fragment . getGeneratedPosition ( range . end ) ) ; } export function mapCompletionItemToOriginal ( fragment , item ) { if ( ! item . textEdit ) { return item ; } return { ... item , textEdit : mapEditToOriginal ( fragment , item . textEdit ) } ; } export function mapHoverToParent ( fragment , hover ) { if ( ! hover . range ) { return hover ; } return { ... hover , range : mapRangeToOriginal ( fragment , hover . range ) } ; } export function mapObjWithRangeToOriginal < T extends { range : Range ; } > ( fragment , objWithRange ) { return { ... objWithRange , range : mapRangeToOriginal ( fragment , objWithRange . range ) } ; } export function mapInsertReplaceEditToOriginal ( fragment , edit ) { return { ... edit , insert : mapRangeToOriginal ( fragment , edit . insert ) , replace : mapRangeToOriginal ( fragment , edit . replace ) } ; } export function mapEditToOriginal ( fragment , edit ) { return TextEdit . is ( edit ) ? mapObjWithRangeToOriginal ( fragment , edit ) : mapInsertReplaceEditToOriginal ( fragment , edit ) ; } export function mapDiagnosticToGenerated ( fragment , diagnostic ) { return { ... diagnostic , range : mapRangeToGenerated ( fragment , diagnostic . range ) } ; } export function mapColorPresentationToOriginal ( fragment , presentation ) { const item = { ... presentation } ; if ( item . textEdit ) { item . textEdit = mapObjWithRangeToOriginal ( fragment , item . textEdit ) ; } if ( item . additionalTextEdits ) { item . additionalTextEdits = item . additionalTextEdits . map ( ( edit ) => mapObjWithRangeToOriginal ( fragment , edit ) ) ; } return item ; } export function mapSymbolInformationToOriginal ( fragment , info ) { return { ... info , location : mapObjWithRangeToOriginal ( fragment , info . location ) } ; } export function mapLocationLinkToOriginal ( fragment , def ) { return LocationLink . create ( def . targetUri , fragment . getURL ( ) === def . targetUri ? mapRangeToOriginal ( fragment , def . targetRange ) : def . targetRange , fragment . getURL ( ) === def . targetUri ? mapRangeToOriginal ( fragment , def . targetSelectionRange ) : def . targetSelectionRange , def . originSelectionRange ? mapRangeToOriginal ( fragment , def . originSelectionRange ) : undefined ) ; } export function mapTextDocumentEditToOriginal ( fragment , edit ) { if ( edit . textDocument . uri !== fragment . getURL ( ) ) { return edit ; } return TextDocumentEdit . create ( edit . textDocument , edit . edits . map ( ( textEdit ) => mapObjWithRangeToOriginal ( fragment , textEdit ) ) ) ; } export function mapCodeActionToOriginal ( fragment , codeAction ) { return CodeAction . create ( codeAction . title , { documentChanges : codeAction . edit ! . documentChanges ! . map ( ( edit ) => mapTextDocumentEditToOriginal ( fragment , edit as TextDocumentEdit ) ) } , codeAction . kind ) ; } export function mapSelectionRangeToParent ( fragment , selectionRange ) { const { range , parent } = selectionRange ; return SelectionRange . create ( mapRangeToOriginal ( fragment , range ) , parent && mapSelectionRangeToParent ( fragment , parent ) ) ; }