2017-12-05 08:54:17 +00:00
import * as vscode from 'vscode' ;
import { latex_types } from '../dictionary/symbol_types' ;
2017-12-07 13:47:11 +00:00
import preamble_symbols from '../dictionary/preamble_symbols' ;
2017-12-05 08:54:17 +00:00
import text_symbols from '../dictionary/text_symbols' ;
import math_symbols from '../dictionary/math_symbols' ;
import package_symbols from '../dictionary/package_symbols' ;
import environment_symbols from '../dictionary/environment_symbols' ;
2018-01-07 13:44:53 +00:00
import documentclass_symbols from '../dictionary/documentclass_symbols' ;
2017-12-05 08:54:17 +00:00
import tikz_symbols from '../dictionary/tikz_symbols' ;
import parameter_dictionary from '../dictionary/parameter_dictionary' ;
2017-12-07 17:30:43 +00:00
import * as child_process from 'child_process' ;
2017-12-05 08:54:17 +00:00
function convertToItemKind ( type : latex_types ) : vscode . CompletionItemKind {
2018-01-07 13:44:53 +00:00
switch ( type ) {
case latex_types . symbol :
return vscode . CompletionItemKind . Variable ;
case latex_types . function :
return vscode . CompletionItemKind . Function ;
case latex_types . environment :
return vscode . CompletionItemKind . Interface ;
case latex_types . package :
return vscode . CompletionItemKind . Module ;
case latex_types . keyword :
return vscode . CompletionItemKind . Keyword ;
case latex_types . parameter :
return vscode . CompletionItemKind . Value ;
case latex_types . snippet :
return vscode . CompletionItemKind . Snippet ;
default :
return vscode . CompletionItemKind . Property ;
}
2017-12-05 08:54:17 +00:00
}
2017-12-07 13:47:11 +00:00
function createCompletionItem ( label : string , sym_def : Object , match? : string , range? : vscode.Range ) : vscode . CompletionItem {
2018-01-07 13:44:53 +00:00
var item = new vscode . CompletionItem ( label , convertToItemKind ( sym_def [ "kind" ] ) ) ;
if ( sym_def [ "detail" ] ) {
item . detail = sym_def [ "detail" ] ;
}
if ( sym_def [ "documentation" ] ) {
item . documentation = sym_def [ "documentation" ] ;
}
if ( sym_def [ "insertText" ] ) {
if ( sym_def [ "insertText" ] . indexOf ( "$" ) > - 1 ) {
item . insertText = new vscode . SnippetString ( sym_def [ "insertText" ] ) ;
} else {
item . insertText = sym_def [ "insertText" ] ;
}
}
if ( match ) { // if the sortText ins't set, default behaviour ist sorting by label
let index = label . indexOf ( match ) ;
item . sortText = ( index > - 1 ? index : "" ) + label ;
}
if ( range ) { // if no range is set, no replacement will be done just adding the insertText (or label) at cursor position
item . range = range ;
}
return item ;
2017-12-07 13:47:11 +00:00
}
function fillSymbols ( symbolsCollection : vscode.CompletionItem [ ] , symbolsDefinition : Object , match? : string , position? : vscode.Position ) : void {
2018-01-07 13:44:53 +00:00
if ( match ) { // if a match is provided, add a sortText for preordering by match index
if ( position ) {
var range = new vscode . Range ( new vscode . Position ( position . line , position . character - match . length ) , position ) ;
} else {
throw new Error ( "[ CompletionItemProvider | fillSymbols ] Parameter \"position\" not supplied. If parameter \"match\" is supplied, \"position\" has to be supplied to!" ) ;
}
}
for ( var key in symbolsDefinition ) {
if ( match && ! key . includes ( match ) ) { continue ; } // if no match is provided, ignore filtering otherwise ignore the symbol if the match isn't contained
var sym_def = symbolsDefinition [ key ] ;
symbolsCollection . push ( createCompletionItem ( key , sym_def , match , range ) ) ;
if ( sym_def . additionalInserts instanceof Array ) {
for ( var i = 0 ; i < sym_def . additionalInserts . length ; i ++ ) {
var additionalInsert = sym_def . additionalInserts [ i ] ;
if ( typeof additionalInsert === "string" ) {
sym_def . insertText = additionalInsert ;
symbolsCollection . push ( createCompletionItem ( additionalInsert , sym_def , match , range ) ) ;
} else if ( typeof additionalInsert === "object" ) {
sym_def . insertText = additionalInsert [ "insertText" ] ;
if ( additionalInsert . hasOwnProperty ( "kind" ) ) {
sym_def . kind = additionalInsert . kind ;
}
symbolsCollection . push ( createCompletionItem ( additionalInsert [ "label" ] , sym_def , match , range ) ) ;
} else {
throw new Error ( "[ CompletionItemProvider | fillSymbols ] A additionalInsert instance isn't well defined: " + additionalInsert ) ;
}
}
}
}
2017-12-05 08:54:17 +00:00
}
export default class Provider implements vscode . CompletionItemProvider {
2018-01-07 13:44:53 +00:00
private preamble_symbols : vscode.CompletionItem [ ] ;
private text_symbols : vscode.CompletionItem [ ] ;
private math_symbols : vscode.CompletionItem [ ] ;
private package_symbols : vscode.CompletionItem [ ] ;
private documentclass_symbols : vscode.CompletionItem [ ] ;
private environment_symbols : vscode.CompletionItem [ ] ;
private tikz_symbols : vscode.CompletionItem [ ] ;
2017-12-05 08:54:17 +00:00
2018-01-07 13:44:53 +00:00
private parameter_dictionary : Object ;
2017-12-05 08:54:17 +00:00
2018-01-07 13:44:53 +00:00
private environment_type_dict = {
"displaymath" : "math" , "equation" : "math" , "eqnarray" : "math" , "align" : "math" , "align*" : "math" ,
"multline" : "math" , "multline*" : "math" , "gather" : "math" , "gather*" : "math" , "split" : "math" , "split*" : "math" ,
"tikzpicture" : "tikz" ,
"document" : "text"
} ;
2017-12-07 13:47:11 +00:00
2017-12-05 08:54:17 +00:00
constructor ( ) {
2018-01-07 13:44:53 +00:00
// initialize Collections
this . preamble_symbols = new Array < vscode.CompletionItem > ( ) ;
this . text_symbols = new Array < vscode.CompletionItem > ( ) ;
this . math_symbols = new Array < vscode.CompletionItem > ( ) ;
this . package_symbols = new Array < vscode.CompletionItem > ( ) ;
this . environment_symbols = new Array < vscode.CompletionItem > ( ) ;
this . documentclass_symbols = new Array < vscode.CompletionItem > ( ) ;
this . tikz_symbols = new Array < vscode.CompletionItem > ( ) ;
// fill the collections from the LaTeX symbols definitions
fillSymbols ( this . preamble_symbols , preamble_symbols ) ;
fillSymbols ( this . text_symbols , text_symbols ) ;
fillSymbols ( this . math_symbols , math_symbols ) ;
fillSymbols ( this . package_symbols , package_symbols ) ;
fillSymbols ( this . environment_symbols , environment_symbols ) ;
fillSymbols ( this . documentclass_symbols , documentclass_symbols ) ;
fillSymbols ( this . tikz_symbols , tikz_symbols ) ;
this . parameter_dictionary = parameter_dictionary ;
}
2017-12-05 08:54:17 +00:00
2018-01-07 13:44:53 +00:00
dispose() { }
2017-12-05 08:54:17 +00:00
2018-01-07 13:44:53 +00:00
private getEnvironmentType ( document : vscode . TextDocument , line_before_pos : string , position : vscode.Position ) : string {
{ // check for inline math
let count = 0 ;
let i : number ;
for ( i = 0 ; i < line_before_pos . length ; i ++ ) {
if ( line_before_pos [ i ] === "$" ) {
count ++ ; // count number of inline math beginnings and endings
}
}
if ( count % 2 === 1 ) { // 2 $ characters represent a closed inline math environment -> odd number is in inline math
return "math" ; // inside inline math -> environment type is math
}
}
// search environment beginings and endings and return type according to environment type
var begin_index = - 1 ;
var end_index = - 1 ;
var environment : string ;
for ( var i = position . line ; i >= 0 ; i -- ) { // iterate over lines from the current line (position) to the top of he document
var line = document . lineAt ( i ) ; // get line with linenumber i (0 based)
if ( line . isEmptyOrWhitespace ) { continue ; } // consistency check
var line_text = line . text ; // get the current line as string
while ( line_text . length > 6 ) { // More then "\\end{" and "}" characters are neccessary for a usefull accessment
begin_index = line_text . lastIndexOf ( "\\begin{" ) ;
end_index = line_text . lastIndexOf ( "\\end{" ) ;
if ( begin_index > end_index ) {
let environment = line_text . substring ( begin_index + 7 , line_text . indexOf ( "}" , begin_index + 7 ) ) ;
if ( this . environment_type_dict . hasOwnProperty ( environment ) ) {
return this . environment_type_dict [ environment ] ; // environment beginning found -> return environment type
}
} else if ( end_index > begin_index ) {
let environment = line_text . substring ( end_index + 5 , line_text . indexOf ( "}" , end_index + 5 ) ) ;
if ( this . environment_type_dict . hasOwnProperty ( environment ) ) {
return "text" ; // environment ending found -> in text area
}
} else {
break ;
}
line_text = line_text . substring ( 0 , begin_index > end_index ? begin_index : end_index ) ;
}
}
return ; // nothing found, return to default behaviour
}
2017-12-05 08:54:17 +00:00
2018-01-07 13:44:53 +00:00
private filterParameters ( symbol_name : string , parameter_match : string , position : vscode.Position ) : vscode . CompletionItem [ ] {
if ( this . parameter_dictionary . hasOwnProperty ( symbol_name ) ) {
var completionItems = new Array < vscode.CompletionItem > ( ) ;
fillSymbols ( completionItems , this . parameter_dictionary [ symbol_name ] , parameter_match , position ) ;
return completionItems ;
}
return [ ] ;
}
2017-12-05 08:54:17 +00:00
2018-01-07 13:44:53 +00:00
private searchAndCreateLableSymbols ( document : vscode . TextDocument , symbol_lable : string , position : vscode.Position ) : vscode . CompletionItem [ ] {
var completionItems = new Array < vscode.CompletionItem > ( ) ;
for ( var i = 0 ; i < document . lineCount ; i ++ ) {
var line = document . lineAt ( i ) ;
if ( line . isEmptyOrWhitespace ) { continue ; }
var line_text = line . text ;
var index_start = line_text . indexOf ( "\\label{" ) + 7 ; // add 7 to considure the length of "\label{"
if ( index_start > 6 ) { // index_start returns > -1 iff substring was found, adding 7 => 6
var index_end = line_text . indexOf ( "}" , index_start ) ;
if ( index_end > - 1 ) {
completionItems . push ( new vscode . CompletionItem ( line_text . substring ( index_start , index_end ) , vscode . CompletionItemKind . Reference ) ) ;
}
}
}
return completionItems ;
}
2017-12-05 08:54:17 +00:00
2018-01-07 13:44:53 +00:00
private filterSymbols ( symbolsCollection : vscode.CompletionItem [ ] , match : string , position : vscode.Position ) : vscode . CompletionItem [ ] {
if ( ! match || match . length === 0 ) { // no filtering neccessary (empty string is substring of all lables) -> return all
return symbolsCollection ;
}
var filtert_symbols = new Array < vscode.CompletionItem > ( ) ;
var range = new vscode . Range ( new vscode . Position ( position . line , position . character - match . length ) , position ) ;
for ( var i = 0 ; i < symbolsCollection . length ; i ++ ) {
var item = symbolsCollection [ i ] ;
var clonedItem ;
if ( item . label . includes ( match ) ) {
clonedItem = new vscode . CompletionItem ( item . label , item . kind ) ;
if ( item . documentation ) {
clonedItem . documentation = item . documentation ;
}
if ( item . insertText ) {
clonedItem . insertText = item . insertText ;
}
if ( range ) {
clonedItem . range = range ;
}
clonedItem . filterText = match ;
clonedItem . sortText = item . label . indexOf ( match ) + item . label ;
filtert_symbols . push ( clonedItem ) ;
}
}
return filtert_symbols ;
}
2017-12-07 17:30:43 +00:00
2018-01-07 13:44:53 +00:00
private getFileNames ( path : string , resolve : ( completionItems : vscode.CompletionItem [ ] ) = > void , reject : ( message : string ) = > void ) : void {
child_process . exec ( "ls " + path + " -t" , ( error : Error , stdout : string , stderr : string ) = > {
var fileNames = stdout . split ( "\n" ) ;
var completionItems = new Array < vscode.CompletionItem > ( ) ;
for ( var i = 0 ; i < fileNames . length ; i ++ ) {
completionItems . push ( new vscode . CompletionItem ( fileNames [ i ] , vscode . CompletionItemKind . File ) ) ;
}
resolve ( completionItems ) ;
} ) ;
}
2017-12-07 17:30:43 +00:00
2018-01-07 13:44:53 +00:00
provideCompletionItems ( document : vscode . TextDocument , position : vscode.Position , token : vscode.CancellationToken ) : Thenable < vscode.CompletionItem [ ] > {
return new Promise < vscode.CompletionItem [ ] > ( ( resolve , reject ) = > {
var line = document . lineAt ( position ) ;
if ( line . isEmptyOrWhitespace ) {
return resolve ( [ ] ) ;
}
var line_text = line . text ;
var line_before_pos = line_text . substring ( 0 , position . character ) . trim ( ) ;
2017-12-07 17:30:43 +00:00
2018-01-07 13:44:53 +00:00
var start_index , end_index : number ;
var symbol_lable , symbol_parameter : string ;
if ( ( start_index = line_before_pos . lastIndexOf ( "\\" ) + 1 ) > 0 ) {
if ( ( end_index = line_before_pos . indexOf ( "{" , start_index ) ) > - 1 ) {
symbol_lable = line_before_pos . substring ( start_index , end_index ) ;
symbol_parameter = line_before_pos . substring ( end_index + 1 ) ;
if ( ( end_index = symbol_lable . indexOf ( "[" ) ) > - 1 ) { // check for optional symbol parameters and ignore them
symbol_lable = symbol_lable . substring ( 0 , end_index ) ;
}
switch ( symbol_lable ) {
case "begin" :
case "end" :
return resolve ( this . filterSymbols ( this . environment_symbols , symbol_parameter , position ) ) ;
case "ref" :
case "eqref" :
case "pageref" :
return resolve ( this . searchAndCreateLableSymbols ( document , symbol_parameter , position ) ) ;
case "usepackage" : // provide package symbols by first all usepackage parameters before the last ","
return resolve ( this . filterSymbols ( this . package_symbols , symbol_parameter . substring ( symbol_parameter . lastIndexOf ( "," ) + 1 ) . trim ( ) , position ) ) ;
case "documentclass" :
return resolve ( this . filterSymbols ( this . documentclass_symbols , symbol_parameter , position ) ) ;
case "input" :
case "include" :
case "includegraphics" :
case "lstinputlisting" :
return this . getFileNames ( symbol_parameter , resolve , reject ) ; // runs async -> let getFileNames resolve / reject the promise
default : // default to NO suggestions
return resolve ( [ ] ) ;
}
} else if ( ( end_index = line_before_pos . indexOf ( "[" , start_index ) ) > - 1 ) {
symbol_lable = line_before_pos . substring ( start_index , end_index ) ;
symbol_parameter = line_before_pos . substring ( end_index + 1 )
return resolve ( this . filterParameters ( symbol_lable , symbol_parameter . substring ( symbol_parameter . lastIndexOf ( "," ) + 1 ) . trim ( ) , position ) ) ;
} else {
symbol_lable = line_before_pos . substring ( start_index ) ;
switch ( this . getEnvironmentType ( document , line_before_pos , position ) ) {
case "math" :
return resolve ( this . filterSymbols ( this . math_symbols , symbol_lable , position ) ) ;
case "tikz" :
return resolve ( this . filterSymbols ( this . tikz_symbols , symbol_lable , position ) ) ;
case "text" :
return resolve ( this . filterSymbols ( this . text_symbols , symbol_lable , position ) ) ;
default :
return resolve ( this . filterSymbols ( this . preamble_symbols , symbol_lable , position ) ) ;
}
}
}
resolve ( [ ] ) ;
} ) . then ( ( res ) = > res ) ;
2017-12-05 08:54:17 +00:00
}
2018-01-07 13:44:53 +00:00
}