import * as path from "path" ; import * as vscode from "vscode" ; import { applyWorkspaceEdit } from "../utils/editUtils" ; import { registerCommand } from "../utils/uiUtils" ; import { executeJavaLanguageServerCommand , getJavaExtension , isJavaExtActivated } from "./commands" ; const UNDEFINED_TYPE = "16777218" ; const UNDEFINED_NAME = "570425394" ; const COMMAND_SEARCH_ARTIFACT = "maven.artifactSearch" ; const TITLE_RESOLVE_UNKNOWN_TYPE = "Resolve unknown type" ; export function registerArtifactSearcher ( context ) { const javaExt = getJavaExtension ( ) ; if ( ! ! javaExt ) { const resolver = new TypeResolver ( path . join ( context . extensionPath , "resources" , "IndexData" ) ) ; registerCommand ( context , COMMAND_SEARCH_ARTIFACT , async ( param ) => await resolver . pickAndAddDependency ( param ) ) ; context . subscriptions . push ( vscode . languages . registerHoverProvider ( "java" , { provideHover ( document , position , _token ) { return resolver . getArtifactsHover ( document , position ) ; } } ) ) ; context . subscriptions . push ( vscode . languages . registerCodeActionsProvider ( "java" , { provideCodeActions ( document , range , codeActionContext , _token ) { return resolver . getArtifactsCodeActions ( document , codeActionContext , range ) ; } } ) ) ; } } class TypeResolver { private dataPath : string ; private initialized : boolean = false ; constructor ( dataPath ) { this . dataPath = dataPath ; } public async initialize ( ) { if ( ! this . initialized ) { try { await executeJavaLanguageServerCommand ( "java.maven.initializeSearcher" , this . dataPath ) ; this . initialized = true ; } catch ( error ) { } } } public getArtifactsHover ( document , position ) { if ( ! isJavaExtActivated ( ) ) { return undefined ; } if ( ! this . initialized ) { this . initialize ( ) . catch ( ) ; return undefined ; } const diagnostics = vscode . languages . getDiagnostics ( document . uri ) . filter ( diagnostic => { return diagnosticIndicatesUnresolvedType ( diagnostic , document ) && position . isAfterOrEqual ( diagnostic . range . start ) && position . isBeforeOrEqual ( diagnostic . range . end ) ; } ) ; if ( diagnostics . length > 0 ) { const diagnostic = diagnostics [ 0 ] ; const line = diagnostic . range . start . line ; const character = diagnostic . range . start . character ; const className = document . getText ( diagnostic . range ) ; const length = document . offsetAt ( diagnostic . range . end ) - document . offsetAt ( diagnostic . range . start ) ; const param = { className , uri : encodeBase64 ( document . uri . toString ( ) ) , line , character , length } ; const commandName = TITLE_RESOLVE_UNKNOWN_TYPE ; const message = `\uD83D\uDC49 [${ commandName }](command:${ COMMAND_SEARCH_ARTIFACT }?${ encodeURIComponent ( JSON . stringify ( param ) ) } "${ commandName }")` ; const hoverMessage = new vscode . MarkdownString ( message ) ; hoverMessage . isTrusted = true ; return new vscode . Hover ( hoverMessage ) ; } else { return undefined ; } } public getArtifactsCodeActions ( document , context , _selectRange ) { if ( ! isJavaExtActivated ( ) ) { return undefined ; } if ( ! this . initialized ) { this . initialize ( ) . catch ( ) ; return undefined ; } const diagnostics = context . diagnostics . filter ( diagnostic => { return diagnosticIndicatesUnresolvedType ( diagnostic , document ) ; } ) ; if ( diagnostics . length > 0 ) { const range = diagnostics [ 0 ] . range ; const className = document . getText ( range ) ; const uri = document . uri . toString ( ) ; const line = range . start . line ; const character = range . start . character ; const length = document . offsetAt ( range . end ) - document . offsetAt ( range . start ) ; const command = { title : TITLE_RESOLVE_UNKNOWN_TYPE , command : COMMAND_SEARCH_ARTIFACT , arguments : [ { className , uri : encodeBase64 ( uri ) , line , character , length } ] } ; const codeAction = { title : `${ TITLE_RESOLVE_UNKNOWN_TYPE } '${ className }'` , command , kind : vscode . CodeActionKind . QuickFix } ; return [ codeAction ] ; } else { return [ ] ; } } public async pickAndAddDependency ( param ) { if ( ! isJavaExtActivated ( ) ) { return ; } if ( ! this . initialized ) { this . initialize ( ) . catch ( ) ; return ; } const pickItem = await vscode . window . showQuickPick ( getArtifactsPickItems ( param . className ) , { placeHolder : "Select the artifact you want to add" } ) ; if ( pickItem === undefined ) { return ; } param . uri = decodeBase64 ( param . uri ) ; const edits = await getWorkSpaceEdits ( pickItem , param ) ; await applyEdits ( vscode . Uri . parse ( param . uri ) , edits ) ; } } async function getArtifactsPickItems ( className ) { const searchParam = { searchType : SearchType . className , className } ; const response = await executeJavaLanguageServerCommand ( "java.maven.searchArtifact" , searchParam ) ; const picks = [ ] ; for ( let i = 0 ; i < Math . min ( Math . round ( response . length / 5 ) , 5 ) ; i += 1 ) { const arr = [ response [ i ] . groupId , " : " , response [ i ] . artifactId , " : " , response [ i ] . version ] ; picks . push ( { label : `$(thumbsup)  ${ response [ i ] . className }` , description : response [ i ] . fullClassName , detail : arr . join ( "" ) } ) ; } for ( let i = Math . min ( Math . round ( response . length / 5 ) , 5 ) ; i < response . length ; i += 1 ) { const arr = [ response [ i ] . groupId , " : " , response [ i ] . artifactId , " : " , response [ i ] . version ] ; picks . push ( { label : response [ i ] . className , description : response [ i ] . fullClassName , detail : arr . join ( "" ) } ) ; } return picks ; } async function applyEdits ( uri , edits ) { if ( Object . keys ( edits [ 2 ] . changes ) . length > 0 ) { await applyWorkspaceEdit ( edits [ 0 ] ) ; await applyWorkspaceEdit ( edits [ 1 ] ) ; let document = await vscode . workspace . openTextDocument ( uri ) ; document . save ( ) ; if ( edits [ 2 ] . changes [ Object . keys ( edits [ 2 ] . changes ) [ 0 ] ] . length === 0 ) { return ; } await applyWorkspaceEdit ( edits [ 2 ] ) ; document = await vscode . workspace . openTextDocument ( vscode . Uri . parse ( Object . keys ( edits [ 2 ] . changes ) [ 0 ] ) ) ; document . save ( ) ; const LINE_OFFSET = 1 ; const startLine = edits [ 2 ] . changes [ Object . keys ( edits [ 2 ] . changes ) [ 0 ] ] [ 0 ] . range . start . line + LINE_OFFSET ; const lineNumber = edits [ 2 ] . changes [ Object . keys ( edits [ 2 ] . changes ) [ 0 ] ] [ 0 ] . newText . indexOf ( "<dependencies>" ) === - 1 ? 5 : 7 ; const editor = await vscode . window . showTextDocument ( document , { selection : new vscode . Range ( startLine , 0 , startLine + lineNumber , 0 ) , preview : false } ) ; editor . revealRange ( new vscode . Range ( startLine , 0 , startLine + lineNumber , 0 ) , vscode . TextEditorRevealType . InCenter ) ; } else { vscode . window . showInformationMessage ( "Sorry, the pom.xml file is inexistent or invalid." ) ; } } async function getWorkSpaceEdits ( pickItem , param ) { return await executeJavaLanguageServerCommand ( "java.maven.addDependency" , pickItem . description , pickItem . detail , param . uri , param . line , param . character , param . length ) ; } function startsWithCapitalLetter ( word ) { return word . charCodeAt ( 0 ) >= 65 && word . charCodeAt ( 0 ) <= 90 ; } function diagnosticIndicatesUnresolvedType ( diagnostic , document ) { return ( UNDEFINED_TYPE === diagnostic . code || UNDEFINED_NAME === diagnostic . code && startsWithCapitalLetter ( document . getText ( diagnostic . range ) ) ) ; } function encodeBase64 ( content ) { return Buffer . from ( content , "utf8" ) . toString ( "base64" ) ; } function decodeBase64 ( content ) { return Buffer . from ( content , "base64" ) . toString ( "utf8" ) ; } export interface IArtifactSearchResult { groupId : string ; artifactId : string ; version : string ; className : string ; fullClassName : string ; usage : number ; kind : number ; } export enum SearchType { className = "CLASSNAME" , identifier = "IDENTIFIER" } export interface ISearchArtifactParam { searchType : SearchType ; className ? : string ; groupId ? : string ; artifactId ? : string ; }