import { commands , CompletionItem , CompletionItemKind , ExtensionContext , languages , Position , Range , SnippetString , TextEdit , window , TextDocument , CompletionContext , CancellationToken , ProviderResult , CompletionList } from 'vscode' ; import { Disposable , LanguageClientOptions , ProvideCompletionItemsSignature , NotificationType , CommonLanguageClient } from 'vscode-languageclient' ; import * as nls from 'vscode-nls' ; import { getCustomDataSource } from './customData' ; import { RequestService , serveFileSystemRequests } from './requests' ; namespace CustomDataChangedNotification { export const type = new NotificationType ( 'css/customDataChanged' ) ; } const localize = nls . loadMessageBundle ( ) ; export type LanguageClientConstructor = ( name : string , description : string , clientOptions : LanguageClientOptions ) => CommonLanguageClient ; export interface Runtime { TextDecoder : { new ( encoding ? ) : { decode ( buffer ) : string ; } ; } ; fs ? : RequestService ; } export function startClient ( context , newLanguageClient , runtime ) { const customDataSource = getCustomDataSource ( context . subscriptions ) ; let documentSelector = [ 'css' , 'scss' , 'less' ] ; let clientOptions = { documentSelector , synchronize : { configurationSection : [ 'css' , 'scss' , 'less' ] } , initializationOptions : { handledSchemas : [ 'file' ] } , middleware : { provideCompletionItem ( document , position , context , token , next ) { function updateRanges ( item ) { const range = item . range ; if ( range instanceof Range && range . end . isAfter ( position ) && range . start . isBeforeOrEqual ( position ) ) { item . range = { inserting : new Range ( range . start , position ) , replacing : range } ; } } function updateLabel ( item ) { if ( item . kind === CompletionItemKind . Color ) { item . label = { label : item . label as string , description : ( item . documentation as string ) } ; } } function updateProposals ( r ) { if ( r ) { ( Array . isArray ( r ) ? r : r . items ) . forEach ( updateRanges ) ; ( Array . isArray ( r ) ? r : r . items ) . forEach ( updateLabel ) ; } return r ; } const isThenable = < T > ( obj ) : obj is Thenable < T > => obj && ( < any > obj ) [ 'then' ] ; const r = next ( document , position , context , token ) ; if ( isThenable < CompletionItem [ ] | CompletionList | null | undefined > ( r ) ) { return r . then ( updateProposals ) ; } return updateProposals ( r ) ; } } } ; let client = newLanguageClient ( 'css' , localize ( 'cssserver.name' , 'CSS Language Server' ) , clientOptions ) ; client . registerProposedFeatures ( ) ; client . onReady ( ) . then ( ( ) => { client . sendNotification ( CustomDataChangedNotification . type , customDataSource . uris ) ; customDataSource . onDidChange ( ( ) => { client . sendNotification ( CustomDataChangedNotification . type , customDataSource . uris ) ; } ) ; serveFileSystemRequests ( client , runtime ) ; } ) ; let disposable = client . start ( ) ; context . subscriptions . push ( disposable ) ; client . onReady ( ) . then ( ( ) => { context . subscriptions . push ( initCompletionProvider ( ) ) ; } ) ; function initCompletionProvider ( ) { const regionCompletionRegExpr = /^(\s*)(\/(\*\s*(#\w*)?)?)?$/ ; return languages . registerCompletionItemProvider ( documentSelector , { provideCompletionItems ( doc , pos ) { let lineUntilPos = doc . getText ( new Range ( new Position ( pos . line , 0 ) , pos ) ) ; let match = lineUntilPos . match ( regionCompletionRegExpr ) ; if ( match ) { let range = new Range ( new Position ( pos . line , match [ 1 ] . length ) , pos ) ; let beginProposal = new CompletionItem ( '#region' , CompletionItemKind . Snippet ) ; beginProposal . range = range ; TextEdit . replace ( range , '/* #region */' ) ; beginProposal . insertText = new SnippetString ( '/* #region $1*/' ) ; beginProposal . documentation = localize ( 'folding.start' , 'Folding Region Start' ) ; beginProposal . filterText = match [ 2 ] ; beginProposal . sortText = 'za' ; let endProposal = new CompletionItem ( '#endregion' , CompletionItemKind . Snippet ) ; endProposal . range = range ; endProposal . insertText = '/* #endregion */' ; endProposal . documentation = localize ( 'folding.end' , 'Folding Region End' ) ; endProposal . sortText = 'zb' ; endProposal . filterText = match [ 2 ] ; return [ beginProposal , endProposal ] ; } return null ; } } ) ; } commands . registerCommand ( '_css.applyCodeAction' , applyCodeAction ) ; function applyCodeAction ( uri , documentVersion , edits ) { let textEditor = window . activeTextEditor ; if ( textEditor && textEditor . document . uri . toString ( ) === uri ) { if ( textEditor . document . version !== documentVersion ) { window . showInformationMessage ( `CSS fix is outdated and can't be applied to the document.` ) ; } textEditor . edit ( mutator => { for ( let edit of edits ) { mutator . replace ( client . protocol2CodeConverter . asRange ( edit . range ) , edit . newText ) ; } } ) . then ( success => { if ( ! success ) { window . showErrorMessage ( 'Failed to apply CSS fix to the document. Please consider opening an issue with steps to reproduce.' ) ; } } ) ; } } }