import * as _ from "lodash" ; import * as vscode from "vscode" ; import { ElementNode , getCurrentNode , XmlTagName } from "../utils/lexerUtils" ; import { centralProvider } from "./centralProvider" ; import { COMMAND_COMPLETION_ITEM_SELECTED } from "./constants" ; import { indexProvider } from "./indexProvider" ; import { localProvider } from "./localProvider" ; const artifactSegments = [ "\t<groupId>$1</groupId>" , "\t<artifactId>$2</artifactId>" , "\t<version>$3</version>" ] ; const dependencySnippetString = [ "<dependency>" , ... artifactSegments , "</dependency>" ] . join ( "\n" ) ; const pluginSnippetString = [ "<plugin>" , ... artifactSegments , "</plugin>" ] . join ( "\n" ) ; const DEFAULT_GROUP_ID = "org.apache.maven.plugins" ; class CompletionProvider implements vscode . CompletionItemProvider { public async provideCompletionItems ( document , position , _token , _context ) { const documentText = document . getText ( ) ; const cursorOffset = document . offsetAt ( position ) ; const currentNode = getCurrentNode ( documentText , cursorOffset ) ; if ( currentNode === undefined || currentNode . contentStart === undefined ) { return undefined ; } const targetRange = new vscode . Range ( document . positionAt ( currentNode . contentStart ) , position ) ; switch ( currentNode . tag ) { case XmlTagName . GroupId : { const siblingNodes = _ . get ( currentNode , "parent.children" , [ ] ) ; const artifactIdNode = siblingNodes . find ( elem => elem . tag === XmlTagName . ArtifactId ) ; const groupIdHint = currentNode . text ? currentNode . text : "" ; const artifactIdHint = artifactIdNode && artifactIdNode . text ? artifactIdNode . text : "" ; const centralItems = await centralProvider . getGroupIdCandidates ( groupIdHint , artifactIdHint ) ; const indexItems = await indexProvider . getGroupIdCandidates ( groupIdHint , artifactIdHint ) ; const localItems = await localProvider . getGroupIdCandidates ( groupIdHint , artifactIdHint ) ; const mergedItems = this . deDuplicate ( centralItems , indexItems , localItems , ( item ) => item . insertText ) ; mergedItems . forEach ( item => item . range = targetRange ) ; return new vscode . CompletionList ( mergedItems , _ . isEmpty ( centralItems ) ) ; } case XmlTagName . ArtifactId : { const siblingNodes = _ . get ( currentNode , "parent.children" , [ ] ) ; const groupIdNode = siblingNodes . find ( elem => elem . tag === XmlTagName . GroupId ) ; const groupIdHint = groupIdNode && groupIdNode . text ? groupIdNode . text : "" ; const artifactIdHint = currentNode . text ? currentNode . text : "" ; const centralItems = await centralProvider . getArtifactIdCandidates ( groupIdHint , artifactIdHint ) ; const indexItems = await indexProvider . getArtifactIdCandidates ( groupIdHint , artifactIdHint ) ; const localItems = await localProvider . getArtifactIdCandidates ( groupIdHint , artifactIdHint ) ; let mergedItems = [ ] ; const ID_SEPARATOR = ":" ; mergedItems = this . deDuplicate ( centralItems , indexItems , localItems , ( item ) => _ . get ( item , "data.groupId" ) + ID_SEPARATOR + item . insertText ) ; mergedItems = this . reserveGidMatch ( mergedItems , groupIdHint ) ; if ( groupIdNode && groupIdNode . contentStart !== undefined && groupIdNode . contentEnd !== undefined ) { for ( const item of mergedItems ) { const matchedGroupId = _ . get ( item , "data.groupId" ) ; if ( matchedGroupId ) { const groupIdRange = new vscode . Range ( document . positionAt ( groupIdNode . contentStart ) , document . positionAt ( groupIdNode . contentEnd ) ) ; item . additionalTextEdits = [ new vscode . TextEdit ( groupIdRange , matchedGroupId ) ] ; } } } mergedItems . forEach ( item => item . range = targetRange ) ; return new vscode . CompletionList ( mergedItems , _ . isEmpty ( centralItems ) ) ; } case XmlTagName . Version : { const siblingNodes = _ . get ( currentNode , "parent.children" , [ ] ) ; const groupIdNode = siblingNodes . find ( elem => elem . tag === XmlTagName . GroupId ) ; const artifactIdNode = siblingNodes . find ( elem => elem . tag === XmlTagName . ArtifactId ) ; const groupIdHint = groupIdNode ?. text || DEFAULT_GROUP_ID ; const artifactIdHint = artifactIdNode ?. text ; if ( ! groupIdHint || ! artifactIdHint ) { return [ ] ; } const centralItems = await centralProvider . getVersionCandidates ( groupIdHint , artifactIdHint ) ; const indexItems = await indexProvider . getVersionCandidates ( groupIdHint , artifactIdHint ) ; const localItems = await localProvider . getVersionCandidates ( groupIdHint , artifactIdHint ) ; const mergedItems = this . deDuplicate ( centralItems , indexItems , localItems , ( item ) => item . insertText ) ; mergedItems . forEach ( item => item . range = targetRange ) ; return new vscode . CompletionList ( mergedItems , false ) ; } case XmlTagName . Dependencies : { const snippetItem = new vscode . CompletionItem ( "dependency" , vscode . CompletionItemKind . Snippet ) ; const snippetContent = trimBrackets ( dependencySnippetString , documentText , cursorOffset ) ; const dependencySnippet = new vscode . SnippetString ( snippetContent ) ; snippetItem . insertText = dependencySnippet ; snippetItem . detail = "Maven Snippet" ; snippetItem . command = { title : "selected" , command : COMMAND_COMPLETION_ITEM_SELECTED , arguments : [ { completeFor : "dependency" , source : "snippet" } ] } ; return new vscode . CompletionList ( [ snippetItem ] , false ) ; } case XmlTagName . Plugins : { const snippetItem = new vscode . CompletionItem ( "plugin" , vscode . CompletionItemKind . Snippet ) ; const snippetContent = trimBrackets ( pluginSnippetString , documentText , cursorOffset ) ; const pluginSnippet = new vscode . SnippetString ( snippetContent ) ; snippetItem . insertText = pluginSnippet ; snippetItem . detail = "Maven Snippet" ; snippetItem . command = { title : "selected" , command : COMMAND_COMPLETION_ITEM_SELECTED , arguments : [ { completeFor : "plugin" , source : "snippet" } ] } ; return new vscode . CompletionList ( [ snippetItem ] , false ) ; } default : return undefined ; } } private deDuplicate ( first , second , third , valueIteratee ) { return _ . unionBy ( first , second , third , valueIteratee ) ; } private reserveGidMatch ( items , groupIdHint ) { const reservedItems = items . filter ( item => { return _ . get ( item , "data.groupId" ) === groupIdHint ; } ) ; const reservedArtifactId = reservedItems . map ( ( item ) => { return item . insertText ; } ) ; items = items . filter ( ( item ) => { return reservedArtifactId . indexOf ( item . insertText ) === - 1 ; } ) ; items = items . concat ( reservedItems ) ; return items ; } } function trimBrackets ( snippetContent , fileContent , offset ) { let ret = snippetContent ; const sectionStart = fileContent . lastIndexOf ( ">" , offset - 1 ) + 1 ; const preChars = fileContent . slice ( sectionStart , offset ) . trim ( ) ; if ( preChars . startsWith ( "<" ) ) { ret = ret . slice ( 1 , ret . length ) ; } const postChar = fileContent [ offset ] ; if ( postChar === ">" ) { ret = ret . slice ( 0 , ret . length - 1 ) ; } return ret ; } export const completionProvider = new CompletionProvider ( ) ;