import { CompletionItemProvider , CompletionItem , CompletionItemKind , TextDocument , Position , CancellationToken , Range , workspace , SnippetString } from 'vscode' ; import { StylusNode , buildAst , flattenAndFilterAst , isFunctionNode , isSelectorCallNode , isSelectorNode , isVariableNode } from './parser' ; import { compact , prepareName } from './utils' ; import * as cssSchema from './css-schema' ; import cssBuiltIn from './css-built-in' ; import builtIn from './built-in' ; import { getPropertyDescription } from './languageFacts' ; param { String } currentWord return { Boolean } export function isClassOrId ( currentWord ) { return currentWord . startsWith ( '.' ) || currentWord . startsWith ( '#' ) || currentWord . startsWith ( '&' ) ; } param { String } currentWord return { Boolean } export function isAtRule ( currentWord ) { return currentWord . startsWith ( '\@' ) ; } param { Object } cssSchema param { String } currentWord return { Boolean } export function isValue ( cssSchema , currentWord ) { const property = getPropertyName ( currentWord ) ; return property && Boolean ( findPropertySchema ( cssSchema , property ) ) ; } param { String } currentWord return { String } export function getPropertyName ( currentWord ) { return currentWord . trim ( ) . replace ( ':' , ' ' ) . split ( ' ' ) [ 0 ] ; } param { Object } cssSchema param { String } property return { Object } export function findPropertySchema ( cssSchema , property ) { return cssSchema . cssData . properties . find ( item => item . name === property ) ; } param { Object } node param { String [ ] } text return { SymbolInformation } function _variableSymbol ( node , text , currentWord ) { const name = node . name ; const lineno = Number ( node . val . lineno ) - 1 ; const completionItem = new CompletionItem ( name ) ; completionItem . detail = text [ lineno ] . trim ( ) ; completionItem . kind = CompletionItemKind . Variable ; return completionItem ; } param { Object } node param { String [ ] } text return { CompletionItem } function _functionSymbol ( node , text ) { const name = node . name ; const completionItem = new CompletionItem ( name ) ; completionItem . kind = CompletionItemKind . Function ; return completionItem ; } param { Object } node param { String [ ] } text param { String } currentWord return { CompletionItem } function _selectorSymbol ( node , text , currentWord ) { const firstSegment = node . segments [ 0 ] ; const name = firstSegment . string ? node . segments . map ( s => s . string ) . join ( '' ) : firstSegment . nodes . map ( s => s . name ) . join ( '' ) ; const completionItem = new CompletionItem ( name ) ; completionItem . kind = CompletionItemKind . Class ; return completionItem ; } param { Object } node param { String [ ] } text return { CompletionItem } function _selectorCallSymbol ( node , text ) { const lineno = Number ( node . lineno ) - 1 ; const name = prepareName ( text [ lineno ] ) ; const completionItem = new CompletionItem ( name ) ; completionItem . kind = CompletionItemKind . Class ; return completionItem ; } param { String } text param { String } currentWord return { CompletionItem } export function getAllSymbols ( text , currentWord ) { const ast = buildAst ( text ) ; const splittedText = text . split ( '\n' ) ; const rawSymbols = flattenAndFilterAst ( ast ) . filter ( item => item && [ 'media' , 'keyframes' , 'atrule' , 'import' , 'require' , 'supports' , 'literal' ] . indexOf ( item . nodeName ) === - 1 ) ; return rawSymbols . map ( item => { if ( isVariableNode ( item ) ) { return _variableSymbol ( item , splittedText , currentWord ) ; } if ( isFunctionNode ( item ) ) { return _functionSymbol ( item , splittedText ) ; } if ( isSelectorNode ( item ) ) { return _selectorSymbol ( item , splittedText , currentWord ) ; } if ( isSelectorCallNode ( item ) ) { return _selectorCallSymbol ( item , splittedText ) ; } } ) ; } param { Object } cssSchema param { String } currentWord return { CompletionItem } export function getAtRules ( cssSchema , currentWord ) { if ( ! isAtRule ( currentWord ) ) return [ ] ; return cssSchema . cssData . atdirectives . map ( property => { const completionItem = new CompletionItem ( property . name ) ; completionItem . detail = property . desc ; completionItem . kind = CompletionItemKind . Keyword ; return completionItem ; } ) ; } param { Object } cssSchema param { String } currentWord return { CompletionItem } export function getProperties ( cssSchema , currentWord , useSeparator ) { if ( isClassOrId ( currentWord ) || isAtRule ( currentWord ) ) return [ ] ; return cssSchema . cssData . properties . map ( property => { const completionItem = new CompletionItem ( property . name ) ; completionItem . insertText = property . name + ( useSeparator ? ': ' : ' ' ) ; completionItem . documentation = getPropertyDescription ( property ) ; completionItem . kind = CompletionItemKind . Property ; return completionItem ; } ) ; } param { Object } cssSchema param { String } currentWord return { CompletionItem } export function getValues ( cssSchema , currentWord ) { const property = getPropertyName ( currentWord ) ; const values = findPropertySchema ( cssSchema , property ) . values ; if ( ! values ) return [ ] ; return values . map ( property => { const completionItem = new CompletionItem ( property . name ) ; completionItem . detail = property . desc ; completionItem . insertText = new SnippetString ( property . name . replace ( ")" , "$0)" ) ) ; completionItem . kind = CompletionItemKind . Value ; return completionItem ; } ) ; } class StylusCompletion implements CompletionItemProvider { provideCompletionItems ( document , position , token ) { const start = new Position ( position . line , 0 ) ; const range = new Range ( start , position ) ; const currentWord = document . getText ( range ) . trim ( ) ; const text = document . getText ( ) ; const value = isValue ( cssSchema , currentWord ) ; const config = workspace . getConfiguration ( 'languageStylus' ) ; let symbols = [ ] , atRules = [ ] , properties = [ ] , values = [ ] ; if ( value ) { values = getValues ( cssSchema , currentWord ) ; symbols = compact ( getAllSymbols ( text , currentWord ) ) . filter ( item => item . kind === CompletionItemKind . Variable ) ; } else { atRules = getAtRules ( cssSchema , currentWord ) ; properties = getProperties ( cssSchema , currentWord , config . get ( 'useSeparator' , true ) ) ; symbols = compact ( getAllSymbols ( text , currentWord ) ) ; } const completions = [ ] . concat ( symbols , atRules , properties , values , cssBuiltIn , config . get ( 'useBuiltinFunctions' , true ) ? builtIn : [ ] ) ; return completions ; } } export default StylusCompletion ;