import { workspace , window , languages , ExtensionContext , TextDocument , DocumentSelector , Position , commands , LanguageConfiguration , CompletionItemKind , CompletionItem , SnippetString , CompletionItemProvider , Hover , HoverProvider , Disposable , CancellationToken } from 'vscode' ; import util = require ( 'util' ) ; import child_process = require ( "child_process" ) ; function strContains ( word , pattern ) { return word . indexOf ( pattern ) > - 1 ; } function strEquals ( word , pattern ) { return word == pattern ; } function config < T > ( key , defaultValue ? ) { const cmake_conf = workspace . getConfiguration ( 'cmake' ) ; return cmake_conf . get < T > ( key , defaultValue ) ; } function commandArgs2Array ( text ) { const re = /^"[^"]*"$/ ; const re2 = /^([^"]|[^"].*?[^"])$/ ; let arr = [ ] ; let argPart = null ; text && text . split ( " " ) . forEach ( function ( arg ) { if ( ( re . test ( arg ) || re2 . test ( arg ) ) && ! argPart ) { arr . push ( arg ) ; } else { argPart = argPart ? argPart + " " + arg : arg ; if ( /"$/ . test ( argPart ) ) { arr . push ( argPart ) ; argPart = null ; } } } ) ; return arr ; } let cmake = ( args ) : Promise < string > => { return new Promise ( function ( resolve , reject ) { let cmake_config = config < string > ( 'cmakePath' , 'cmake' ) ; let cmake_args = commandArgs2Array ( cmake_config ) ; let cmd = child_process . spawn ( cmake_args [ 0 ] , cmake_args . slice ( 1 , cmake_args . length ) . concat ( args . map ( arg => { return arg . replace ( /\r/gm , '' ) ; } ) ) ) ; let stdout = '' ; cmd . stdout . on ( 'data' , function ( data ) { var txt = data . toString ( ) ; stdout += txt . replace ( /\r/gm , '' ) ; } ) ; cmd . on ( "error" , function ( error ) { if ( error && ( < any > error ) . code === 'ENOENT' ) { window . showInformationMessage ( 'The "cmake" command is not found in PATH.  Install it or use `cmake.cmakePath` in the workspace settings to define the CMake executable binary.' ) ; } reject ( ) ; } ) ; cmd . on ( 'exit' , function ( code ) { resolve ( stdout ) ; } ) ; } ) ; } ; function _extractVersion ( output ) { let re = /cmake\s+version\s+(\d+.\d+.\d+)/ ; if ( re . test ( output ) ) { let result = re . exec ( output ) ; return result [ 1 ] ; } return '' ; } async function cmake_version ( ) { let cmd_output = await cmake ( [ '--version' ] ) ; let version = _extractVersion ( cmd_output ) ; return version ; } async function cmake_help_url ( ) { let base_url = 'https://cmake.org/cmake/help' ; let version = await cmake_version ( ) ; if ( version . length > 0 ) { if ( version >= '3.0' ) { let re = /(\d+.\d+).\d+/ ; version = version . replace ( re , '$1/' ) ; } else { let older_versions = [ '2.8.12' , '2.8.11' , '2.8.10' , '2.8.9' , '2.8.8' , '2.8.7' , '2.8.6' , '2.8.5' , '2.8.4' , '2.8.3' , '2.8.2' , '2.8.1' , '2.8.0' , '2.6' ] ; if ( older_versions . indexOf ( version ) == - 1 ) { version = 'latest/' ; } else { version = version + '/cmake.html' ; } } } else { version = 'latest/' ; } return base_url + '/v' + version ; } function cmake_help_command_list ( ) { return cmake ( [ '--help-command-list' ] ) ; } function cmake_help_command ( name ) { return cmake_help_command_list ( ) . then ( function ( result ) { let contains = result . indexOf ( name ) > - 1 ; return new Promise ( function ( resolve , reject ) { if ( contains ) { resolve ( name ) ; } else { reject ( 'not found' ) ; } } ) ; } , function ( e ) { } ) . then ( function ( n ) { return cmake ( [ '--help-command' , n ] ) ; } , null ) ; } function cmake_help_variable_list ( ) { return cmake ( [ '--help-variable-list' ] ) ; } function cmake_help_variable ( name ) { return cmake_help_variable_list ( ) . then ( function ( result ) { let contains = result . indexOf ( name ) > - 1 ; return new Promise ( function ( resolve , reject ) { if ( contains ) { resolve ( name ) ; } else { reject ( 'not found' ) ; } } ) ; } , function ( e ) { } ) . then ( function ( name ) { return cmake ( [ '--help-variable' , name ] ) ; } , null ) ; } function cmake_help_property_list ( ) { return cmake ( [ '--help-property-list' ] ) ; } function cmake_help_property ( name ) { return cmake_help_property_list ( ) . then ( function ( result ) { let contains = result . indexOf ( name ) > - 1 ; return new Promise ( function ( resolve , reject ) { if ( contains ) { resolve ( name ) ; } else { reject ( 'not found' ) ; } } ) ; } , function ( e ) { } ) . then ( function ( name ) { return cmake ( [ '--help-property' , name ] ) ; } , null ) ; } function cmake_help_module_list ( ) { return cmake ( [ '--help-module-list' ] ) ; } function cmake_help_module ( name ) { return cmake_help_module_list ( ) . then ( function ( result ) { let contains = result . indexOf ( name ) > - 1 ; return new Promise ( function ( resolve , reject ) { if ( contains ) { resolve ( name ) ; } else { reject ( 'not found' ) ; } } ) ; } , function ( e ) { } ) . then ( function ( name ) { return cmake ( [ '--help-module' , name ] ) ; } , null ) ; } function cmake_help_all ( ) { let promises = { 'function' : ( name ) => { return cmake_help_command ( name ) ; } , 'module' : ( name ) => { return cmake_help_module ( name ) ; } , 'variable' : ( name ) => { return cmake_help_variable ( name ) ; } , 'property' : ( name ) => { return cmake_help_property ( name ) ; } } ; return promises ; } async function cmake_online_help ( search ) { let url = await cmake_help_url ( ) ; let v2x = url . endsWith ( 'html' ) ; return Promise . all ( [ cmCommandsSuggestionsExact ( search ) , cmVariablesSuggestionsExact ( search ) , cmModulesSuggestionsExact ( search ) , cmPropertiesSuggestionsExact ( search ) , ] ) . then ( function ( results ) { var opener = require ( "opener" ) ; var suggestions = Array . prototype . concat . apply ( [ ] , results ) ; if ( suggestions . length == 0 ) { search = search . replace ( /[<>]/g , '' ) ; if ( v2x || search . length == 0 ) { opener ( url ) ; } else { opener ( url + 'search.html?q=' + search + '&check_keywords=yes&area=default' ) ; } } else { let suggestion = suggestions [ 0 ] ; let type = cmakeTypeFromvscodeKind ( suggestion . kind ) ; if ( type == 'property' ) { if ( v2x ) { opener ( url ) ; } else { opener ( url + 'search.html?q=' + search + '&check_keywords=yes&area=default' ) ; } } else { if ( type == 'function' ) { type = 'command' ; } search = search . replace ( /[<>]/g , '' ) ; if ( v2x ) { opener ( url + '#' + type + ':' + search ) ; } else { opener ( url + type + '/' + search + '.html' ) ; } } } } ) ; } export function activate ( disposables ) { commands . registerCommand ( 'cmake.onlineHelp' , ( ) => { var editor = window . activeTextEditor ; if ( ! editor ) { return ; } var selection = editor . selection ; let document = editor . document ; let position = selection . start ; var currentWord = document . getText ( selection ) ; let wordAtPosition = document . getWordRangeAtPosition ( position ) ; var currentWord = '' ; if ( wordAtPosition && wordAtPosition . start . character < position . character ) { var word = document . getText ( wordAtPosition ) ; currentWord = word ; } window . showInputBox ( { prompt : 'Search on Cmake online documentation' , placeHolder : currentWord } ) . then ( function ( result ) { if ( typeof result != 'undefined' ) { if ( result . length === 0 ) { result = currentWord ; } if ( result != "" ) { cmake_online_help ( result ) ; } } } ) ; } ) ; const CMAKE_LANGUAGE = 'cmake' ; const CMAKE_SELECTOR = [ { language : CMAKE_LANGUAGE , scheme : 'file' } , { language : CMAKE_LANGUAGE , scheme : 'untitled' } , ] ; languages . registerHoverProvider ( CMAKE_SELECTOR , new CMakeExtraInfoSupport ( ) ) ; languages . registerCompletionItemProvider ( CMAKE_SELECTOR , new CMakeSuggestionSupport ( ) ) ; languages . setLanguageConfiguration ( CMAKE_LANGUAGE , { indentationRules : { decreaseIndentPattern : /^(.*\*\/)?\s*\}.*$/ , increaseIndentPattern : /^.*\{[^}"']*$/ } , wordPattern : /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g , comments : { lineComment : '#' } , brackets : [ [ '{' , '}' ] , [ '(' , ')' ] , ] , __electricCharacterSupport : { brackets : [ { tokenType : 'delimiter.curly.ts' , open : '{' , close : '}' , isElectric : true } , { tokenType : 'delimiter.square.ts' , open : '[' , close : ']' , isElectric : true } , { tokenType : 'delimiter.paren.ts' , open : '(' , close : ')' , isElectric : true } ] } , __characterPairSupport : { autoClosingPairs : [ { open : '{' , close : '}' } , { open : '(' , close : ')' } , { open : '"' , close : '"' , notIn : [ 'string' ] } , ] } } ) ; } class CMakeExtraInfoSupport implements HoverProvider { public provideHover ( document , position , token ) { let range = document . getWordRangeAtPosition ( position ) ; let value = document . getText ( range ) ; let promises = cmake_help_all ( ) ; return Promise . all ( [ cmCommandsSuggestionsExact ( value ) , cmVariablesSuggestionsExact ( value ) , cmModulesSuggestionsExact ( value ) , cmPropertiesSuggestionsExact ( value ) , ] ) . then ( function ( results ) { var suggestions = Array . prototype . concat . apply ( [ ] , results ) ; if ( suggestions . length == 0 ) { return null ; } let suggestion = suggestions [ 0 ] ; return promises [ cmakeTypeFromvscodeKind ( suggestion . kind ) ] ( suggestion . label ) . then ( function ( result ) { let lines = result . split ( '\n' ) ; lines = lines . slice ( 2 , lines . length ) ; let hover = new Hover ( { language : 'md' , value : lines . join ( '\n' ) } ) ; return hover ; } ) ; } ) ; } } function vscodeKindFromCMakeCodeClass ( kind ) { switch ( kind ) { case "function" : return CompletionItemKind . Function ; case "variable" : return CompletionItemKind . Variable ; case "module" : return CompletionItemKind . Module ; } return CompletionItemKind . Property ; } function cmakeTypeFromvscodeKind ( kind ) { switch ( kind ) { case CompletionItemKind . Function : return "function" ; case CompletionItemKind . Variable : return "variable" ; case CompletionItemKind . Module : return "module" ; } return "property" ; } function suggestionsHelper ( cmake_cmd , currentWord , type , insertText , matchPredicate ) { return new Promise ( function ( resolve , reject ) { cmake_cmd . then ( function ( stdout ) { let commands = stdout . split ( '\n' ) . filter ( function ( v ) { return matchPredicate ( v , currentWord ) ; } ) ; if ( commands . length > 0 ) { let suggestions = commands . map ( function ( command_name ) { var item = new CompletionItem ( command_name ) ; item . kind = vscodeKindFromCMakeCodeClass ( type ) ; if ( insertText == null || insertText == '' ) { item . insertText = command_name ; } else { let snippet = new SnippetString ( insertText ( command_name ) ) ; item . insertText = snippet ; } return item ; } ) ; resolve ( suggestions ) ; } else { resolve ( [ ] ) ; } } ) . catch ( function ( err ) { reject ( err ) ; } ) ; } ) ; } function cmModuleInsertText ( module ) { if ( module . indexOf ( 'Find' ) == 0 ) { return 'find_package(' + module . replace ( 'Find' , '' ) + '${1: REQUIRED})' ; } else { return 'include(' + module + ')' ; } } function cmFunctionInsertText ( func ) { let scoped_func = [ 'if' , 'function' , 'while' , 'macro' , 'foreach' ] ; let is_scoped = scoped_func . reduceRight ( function ( prev , name , idx , array ) { return prev || func == name ; } , false ) ; if ( is_scoped ) return func + '(${1})\n\t\nend' + func + '(${1})\n' ; else return func + '(${1})' ; } function cmVariableInsertText ( variable ) { return variable . replace ( /<(.*)>/g , '${1:<$1>}' ) ; } function cmPropetryInsertText ( variable ) { return variable . replace ( /<(.*)>/g , '${1:<$1>}' ) ; } function cmCommandsSuggestions ( currentWord ) { let cmd = cmake_help_command_list ( ) ; return suggestionsHelper ( cmd , currentWord , 'function' , cmFunctionInsertText , strContains ) ; } function cmVariablesSuggestions ( currentWord ) { let cmd = cmake_help_variable_list ( ) ; return suggestionsHelper ( cmd , currentWord , 'variable' , cmVariableInsertText , strContains ) ; } function cmPropertiesSuggestions ( currentWord ) { let cmd = cmake_help_property_list ( ) ; return suggestionsHelper ( cmd , currentWord , 'property' , cmPropetryInsertText , strContains ) ; } function cmModulesSuggestions ( currentWord ) { let cmd = cmake_help_module_list ( ) ; return suggestionsHelper ( cmd , currentWord , 'module' , cmModuleInsertText , strContains ) ; } function cmCommandsSuggestionsExact ( currentWord ) { let cmd = cmake_help_command_list ( ) ; return suggestionsHelper ( cmd , currentWord , 'function' , cmFunctionInsertText , strEquals ) ; } function cmVariablesSuggestionsExact ( currentWord ) { let cmd = cmake_help_variable_list ( ) ; return suggestionsHelper ( cmd , currentWord , 'variable' , cmVariableInsertText , strEquals ) ; } function cmPropertiesSuggestionsExact ( currentWord ) { let cmd = cmake_help_property_list ( ) ; return suggestionsHelper ( cmd , currentWord , 'property' , cmPropetryInsertText , strEquals ) ; } function cmModulesSuggestionsExact ( currentWord ) { let cmd = cmake_help_module_list ( ) ; return suggestionsHelper ( cmd , currentWord , 'module' , cmModuleInsertText , strEquals ) ; } class CMakeSuggestionSupport implements CompletionItemProvider { public triggerCharacters : string [ ] ; public excludeTokens : string [ ] = [ 'string' , 'comment' , 'numeric' ] ; public provideCompletionItems ( document , position , token ) { let wordAtPosition = document . getWordRangeAtPosition ( position ) ; var currentWord = '' ; if ( wordAtPosition && wordAtPosition . start . character < position . character ) { var word = document . getText ( wordAtPosition ) ; currentWord = word . substr ( 0 , position . character - wordAtPosition . start . character ) ; } return new Promise ( function ( resolve , reject ) { Promise . all ( [ cmCommandsSuggestions ( currentWord ) , cmVariablesSuggestions ( currentWord ) , cmPropertiesSuggestions ( currentWord ) , cmModulesSuggestions ( currentWord ) ] ) . then ( function ( results ) { var suggestions = Array . prototype . concat . apply ( [ ] , results ) ; resolve ( suggestions ) ; } ) . catch ( err => { reject ( err ) ; } ) ; } ) ; } public resolveCompletionItem ( item , token ) { let promises = cmake_help_all ( ) ; let type = cmakeTypeFromvscodeKind ( item . kind ) ; return promises [ type ] ( item . label ) . then ( function ( result ) { item . documentation = result . split ( '\n' ) [ 3 ] ; return item ; } ) ; } }