Aqui você pode ler o primeiro artigo com a teoria da engenharia de plug-in.
E nesta parte, mostrarei quais problemas encontramos durante a criação do plug-in e como tentamos resolvê-los.

Sobre o que vou falar?
- Parte prática
- UI de várias páginas
- DI em plugins
- Geração de código
- Modificação de código
- O que fazer depois?
- Dicas
- Perguntas frequentes
UI de várias páginas
A primeira coisa que precisamos fazer foi criar uma interface de usuário com várias páginas. Criamos o primeiro formulário complexo com várias marcas de seleção, campos de entrada. Um pouco mais tarde, decidimos adicionar a capacidade de selecionar uma lista de módulos que o usuário pode conectar ao novo módulo. E também queremos escolher os módulos de aplicativo aos quais planejamos conectar o módulo criado.
Ter tantos controles em um formulário não é muito conveniente, por isso eles criaram três páginas separadas, três cortadores de biscoitos separados. Em resumo, a caixa de diálogo do Assistente.

Mas como criar interfaces de usuário de várias páginas em plug-ins é muito doloroso, eu queria encontrar algo pronto. E nas entranhas da IDEA, descobrimos uma classe chamada WizardDialog .

Esta é uma classe de wrapper em uma caixa de diálogo regular, monitorando independentemente o progresso do usuário no assistente e mostrando os botões necessários (Anterior, Próximo, Concluir etc.). Um WizardModel especial é anexado ao WizardDialog , ao qual WizardSteps individuais são adicionados. Cada WizardStep é um formulário separado.
Na sua forma mais simples, a implementação do diálogo é a seguinte:
Assistente de diálogoclass MyWizardDialog( model: MyWizardModel, private val onFinishButtonClickedListener: (MyWizardModel) -> Unit ): WizardDialog<MyWizardModel>(true, true, model) { override fun onWizardGoalAchieved() { super.onWizardGoalAchieved() onFinishButtonClickedListener.invoke(myModel) } }
Herdaremos da classe WizardDialog , parametrizaremos com a classe do nosso WizardModel . Esta classe possui um retorno de chamada especial ( onWizardGoalAchieved ), que informa que o usuário passou pelo assistente até o final e clicou no botão "Concluir".
É importante observar que, dentro desta classe, há uma oportunidade de alcançar apenas o WizardModel . Isso significa que todos os dados que o usuário coletará durante a passagem do assistente devem ser adicionados ao WizardModel .
Wizardmodel class MyWizardModel: WizardModel("Title for my wizard") { init { this.add(MyWizardStep1()) this.add(MyWizardStep2()) this.add(MyWizardStep3()) } }
O modelo é o seguinte: herdamos da classe WizardModel e, usando o método add incorporado, adicionamos WizardSteps separados à caixa de diálogo.
Wizardstep class MyWizardStep1: WizardStep<MyWizardModel>() { private lateinit var contentPanel: JPanel override fun prepare(state: WizardNavigationState?): JComponent { return contentPanel } }
WizardSteps também são simples: herdar da classe WizardStep , parametrizá-lo com a nossa classe model e, o mais importante, substituir o método de preparação, que retorna o componente raiz do seu formulário futuro.
Em termos simples, é realmente assim. Mas no mundo real, provavelmente o seu formulário se parecerá com algo assim:

Aqui você pode se lembrar daqueles momentos em que no mundo Android ainda não sabíamos o que o Clean Architecture, MVP e escrevia todo o código em uma Atividade. Há um novo campo para batalhas arquitetônicas e, se você quiser se confundir, poderá implementar sua própria arquitetura para plugins.
Conclusão
Se você precisar de uma interface de usuário de várias páginas, use o WizardDialog - será mais fácil.
Passamos ao próximo tópico - DI em plugins.
DI em plugins
Por que uma injeção de dependência dentro de um plug-in é necessária?
A primeira razão é a organização da arquitetura dentro do plugin.
Parece, por que geralmente observa algum tipo de arquitetura dentro do plugin? Um plug-in é uma coisa de utilidade, uma vez que eu o escrevi, e é isso, eu esqueci.
Sim mas não
Quando seu plug-in cresce, quando você escreve muito código, a questão do código estruturado surge por si só. Aqui, o DI pode ser útil.
O segundo motivo mais importante - com a ajuda do DI, você pode acessar os componentes escritos pelos desenvolvedores de outros plugins. Pode ser ônibus de eventos, registradores e muito mais.
Apesar de você ser livre para usar qualquer estrutura de DI (Spring, Dagger, etc.), dentro do IntelliJ IDEA, existe sua própria estrutura de DI, que se baseia nos três primeiros níveis de abstração dos quais eu já falei: Aplicativo , Projeto e Módulo .

Cada um desses níveis tem sua própria abstração chamada Component . O componente do nível requerido é criado por instância do objeto desse nível. Portanto, ApplicationComponent é criado uma vez para cada instância da classe Application , da mesma forma que ProjectComponent para instâncias do Project , e assim por diante.
O que precisa ser feito para usar a estrutura de DI?
Primeiro, crie uma classe que implemente um dos componentes de interface necessários - por exemplo, uma classe que implemente ApplicationComponent , ou ProjectComponent , ou ModuleComponent . Ao mesmo tempo, temos a oportunidade de injetar um objeto do nível cuja interface estamos implementando. Ou seja, por exemplo, no ProjectComponent você pode injetar um objeto da classe Project .
Criando classes de componentes class MyAppComponent( val application: Application, val anotherApplicationComponent: AnotherAppComponent ): ApplicationComponent class MyProjectComponent( val project: Project, val anotherProjectComponent: AnotherProjectComponent, val myAppComponent: MyAppComponent ): ProjectComponent class MyModuleComponent( val module: Module, val anotherModuleComponent: AnotherModuleComponent, val myProjectComponent: MyProjectComponent, val myAppComponent: MyAppComponent ): ModuleComponent
Em segundo lugar, é possível injetar outros componentes do mesmo nível ou superior. Ou seja, no ProjectComponent , por exemplo, você pode injetar outro ProjectComponent ou ApplicationComponent . É aqui que você pode acessar instâncias de componentes "alienígenas".
Ao mesmo tempo, o IDEA garante que todo o gráfico de dependência seja montado corretamente, todos os objetos serão criados na ordem correta e inicializados corretamente.
A próxima coisa a fazer é registrar o componente no arquivo plugin.xml . Assim que você implementar uma das interfaces do componente (por exemplo, ApplicationComponent ), o IDEA oferecerá imediatamente o registro do seu componente no plugin.xml.
Registrar o componente no plugin.xml <idea-plugin> ... <project-components> <component> <interface-class> com.experiment.MyProjectComponent </interface-class> <implementation-class> com.experiments.MyProjectComponentImpl </implementation-class> </component> </project-components> </idea-plugin>
Como isso é feito? Uma tag especial <componente de projeto> aparece ( <componente de aplicativo> , <componente de módulo> - dependendo do nível). Há uma tag dentro dela , ela tem mais duas tags: <interface-class> , onde o nome da interface do seu componente é indicado, e <implementation-class> , onde a classe de implementação é indicada. Uma e a mesma classe pode ser uma interface de um componente ou sua implementação, para que você possa fazer com uma única tag <implementation-class> .
A última coisa a fazer é obter o componente do objeto correspondente, ou seja, obtemos o ApplicationComponent da instância do Application , ProjectComponent do Project , etc.
Obter o componente val myAppComponent = application.getComponent(MyAppComponent::class.java) val myProjectComponent = project.getComponent(MyProjectComponent::class.java) val myModuleComponent = module.getComponent(MyModuleComponent::class.java)
Conclusões
- Existe uma estrutura DI dentro do IDEA - não é necessário arrastar nada por conta própria: nem Dagger nem Spring. Embora, é claro, você possa.
- Com este DI, você pode alcançar os componentes prontos, e esse é o próprio suco.
Vamos para a terceira tarefa - geração de código.
Geração de código
Lembre-se, na lista de verificação, tivemos a tarefa de gerar muitos arquivos? Cada vez que criamos um novo módulo, criamos um monte de arquivos: interatores, apresentadores, fragmentos. Ao criar um novo módulo, esses componentes são muito semelhantes entre si e eu gostaria de aprender como gerar essa estrutura automaticamente.
Padrões
Qual é a maneira mais fácil de gerar uma tonelada de código semelhante? Use padrões. Primeiro, você precisa examinar seus modelos e entender quais requisitos são apresentados ao gerador de código.
Uma parte do modelo de arquivo build.gradle apply plugin: 'com.android.library' <if (isKotlinProject) { apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' <if (isModuleWithUI) { apply plugin: 'kotlin-android-extensions' }> }> ... android { ... <if (isMoxyEnabled) { kapt { arguments { arg("moxyReflectorPackage", '<include var="packageName">') } } }> ... } ... dependencies { compileOnly project(':common') compileOnly project(':core-utils') <for (moduleName in enabledModules) { compileOnly project('<include var="moduleName">') }> ... }
Primeiro: queríamos poder usar condições dentro desses padrões. Dou um exemplo: se o plugin estiver de alguma forma conectado à interface do usuário, queremos conectar o plugin especial do Gradle, kotlin-android-extensions .
Condição dentro do modelo <if (isKotlinProject) { apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' <if (isModuleWithUI) { apply plugin: 'kotlin-android-extensions' }> }>
A segunda coisa que queremos é a capacidade de usar uma variável dentro deste modelo. Por exemplo, quando configuramos o kapt para Moxy, queremos inserir o nome do pacote como argumento no processador de anotações.
Substitua o valor da variável dentro do modelo kapt { arguments { arg("moxyReflectorPackage", '<include var="packageName">') } }
Outra coisa que precisamos é a capacidade de lidar com loops dentro do modelo. Lembra-se do formulário em que selecionamos a lista de módulos que queremos conectar ao novo módulo que está sendo criado? Queremos contorná-los em loop e adicionar a mesma linha.
Use o loop no modelo. <for (moduleName in enabledModules) { compileOnly project('<include var="moduleName">') }>
Assim, propusemos três condições para o gerador de código:
- Queremos usar as condições
- Capacidade de substituir valores variáveis
- Precisamos de loops em padrões
Geradores de código
Quais são as opções para implementar o gerador de código? Você pode, por exemplo, escrever seu próprio gerador de código. Por exemplo, os caras do Uber fizeram o seguinte: eles criaram seu próprio plug-in para gerar riblets (as chamadas unidades arquitetônicas). Eles criaram sua própria linguagem de modelo , na qual usavam apenas a capacidade de inserir variáveis. Eles trouxeram as condições ao nível do gerador . Mas pensamos que não faríamos isso.
A segunda opção é usar a classe de utilitário FileTemplateManager incorporada ao IDEA, mas eu não recomendaria isso. Porque ele tem o Velocity como um mecanismo, que tem alguns problemas com o encaminhamento de objetos Java para modelos. Além disso, o FileTemplateManager não pode gerar arquivos diferentes de Java ou XML a partir da caixa. E precisávamos gerar arquivos Groovy, Kotlin, Proguard e outros tipos de arquivos.
A terceira opção foi ... FreeMarker . Se você possui modelos FreeMarker prontos, não se apresse em jogá-los fora - eles podem ser úteis para você dentro do plug-in.
O que precisa ser feito, o que usar o FreeMarker dentro do plugin? Primeiro, adicione modelos de arquivo. Você pode criar a pasta / templates dentro da pasta / resources e adicionar todos os nossos modelos para todos os arquivos - apresentadores, fragmentos etc.

Depois disso, você precisará adicionar uma dependência na biblioteca do FreeMarker. Como o plug-in usa o Gradle, a adição de uma dependência é simples.
Adicionar uma dependência na biblioteca FreeMarker dependencies { ... compile 'org.freemarker:freemarker:2.3.28' }
Depois disso, configure o FreeMarker dentro do nosso plugin. Aconselho que você simplesmente copie essa configuração aqui - ela é torturada, sofrida, copiada e tudo funciona.
Configuração do FreeMarker class TemplatesFactory(val project: Project) : ProjectComponent { private val freeMarkerConfig by lazy { Configuration(Configuration.VERSION_2_3_28).apply { setClassForTemplateLoading( TemplatesFactory::class.java, "/templates" ) defaultEncoding = Charsets.UTF_8.name() templateExceptionHandler = TemplateExceptionHandler.RETHROW_HANDLER logTemplateExceptions = false wrapUncheckedExceptions = true } } ...
É hora de criar arquivos usando o FreeMarker . Para fazer isso, obtemos um modelo da configuração pelo nome e, usando um FileWriter comum, criamos um arquivo com o texto desejado diretamente no disco.
Criando um arquivo através do FileWriter class TemplatesFactory(val project: Project) : ProjectComponent { ... fun generate( pathToFile: String, templateFileName: String, data: Map<String, Any> ) { val template = freeMarkerConfig.getTemplate(templateFileName) FileWriter(pathToFile, false).use { writer -> template.process(data, writer) } } }
E a tarefa parece estar resolvida, mas não. Na parte teórica, mencionei que toda a IDEA está permeada pela estrutura do PSI, e isso deve ser levado em consideração. Se você criar arquivos ignorando a estrutura PSI (por exemplo, através do FileWriter), o IDEA simplesmente não entenderá que você criou algo e não exibirá os arquivos na árvore do projeto. Esperamos cerca de sete minutos antes da IDEA ser indexada e ver os arquivos criados.
Conclusão - faça certo, crie arquivos, levando em consideração a estrutura do PSI.
Crie uma estrutura PSI para arquivos
Para começar, vimos a estrutura de pastas usando o PsiDirectory . O diretório inicial do projeto pode ser obtido usando as funções de extensão guessProjectDir e toPsiDirectory :
Obtenha o projeto PsiDirectory val projectPsiDirectory = project.guessProjectDir()?.toPsiDirectory(project)
Os diretórios subsequentes podem ser encontrados usando o método da classe PsiDirectory findSubdirectory ou criados usando o método createSubdirectory .
Encontre e crie PsiDirectory val coreModuleDir = projectPsiDirectory.findSubdirectory("core") val newModulePsiDir = coreModuleDir.createSubdirectory(config.mainParams.moduleName)
Também recomendo que você crie um mapa a partir do qual é possível obter todas as estruturas de pastas do PsiDirectory usando uma chave de cadeia e, em seguida, adicione os arquivos criados a qualquer uma dessas pastas.
Criar um mapa da estrutura de pastasreturn mutableMapOf <String, PsiDirectory?> (). apply {
this ["root"] = modulePsiDir
this ["src"] = modulePsiDir.createSubdirectory ("src")
this ["main"] = this ["src"]?. createSubdirectory ("main")
this ["java"] = this ["main"]?. createSubdirectory ("java")
this ["res"] = this ["main"]?. createSubdirectory ("res")
// PsiDirectory package name: // ru.hh.feature_worknear → ru / hh / feature_worknear createPackageNameFolder(config) // data this["data"] = this["package"]?.createSubdirectory("data") // ...
}
Pastas criadas. Criaremos PsiFiles usando PsiFileFactory . Esta classe possui um método especial chamado createFileFromText . O método aceita três parâmetros como entrada: nome (String fileName), texto (String text) e tipo (FileType fileType) do arquivo de saída. Dois dos três parâmetros são claros onde obtê-lo: nós sabemos o nome, obtemos o texto do FreeMarker. E onde conseguir o FileType ? E o que é isso tudo?
Tipo de arquivo
FileType é uma classe especial que indica o tipo de arquivo. Somente dois FileType estão disponíveis para nós na "caixa": JavaFileType e XmlFileType, respectivamente para arquivos Java e XML. Mas surge a pergunta: onde obter os tipos para o arquivo build.gradle , para os arquivos Kotlin , para Proguard , para .gitignore , finalmente ?!
Primeiro, a maioria desses FileType s pode ser obtida de outros plug-ins que já foram escritos por alguém. GroovyFileType pode ser obtido no plug-in Groovy , KotlinFileType no plug-in Kotlin , Proguard no plug-in Android .
Como adicionamos a dependência de outro plugin ao nosso? Usamos gradle-intellij-plugin . Ele adiciona um bloco intellij especial ao arquivo build.gradle do plug-in, dentro do qual há uma propriedade especial - plugins . Nesta propriedade, você pode listar a lista de identificadores de plug-in dos quais queremos depender.
Adicionar dependências em outros plugins Pegamos as chaves do repositório oficial do plugin JetBrains . Para plug-ins criados no IDEA (que são Groovy, Kotlin e Android), o nome da pasta do plug-in no IDEA é suficiente. Quanto ao restante, você precisa ir para a página de um plug-in específico no repositório oficial do plug-in JetBrains, a propriedade ID XML do plug - in será indicada lá, assim como a versão (por exemplo, aqui está a página do plug-in Docker ). Leia mais sobre como conectar outros plugins no GitHub .
Em segundo lugar, você precisa adicionar uma descrição de dependência ao arquivo plugin.xml . Isso é feito usando a tag
Conectamos plugins em plugin.xml <idea-plugin> ... <depends>org.jetbrains.android</depends> <depends>org.jetbrains.kotlin</depends> <depends>org.intellij.groovy</depends> </idea-plugin>
Depois de sincronizar o projeto, restringiremos as dependências de outros plugins e poderemos usá-las.
Mas e se não quisermos depender de outros plugins? Nesse caso, podemos criar um esboço para o tipo de arquivo que precisamos. Para fazer isso, primeiro crie uma classe que herdará da classe Language . O identificador exclusivo da nossa linguagem de programação será passado para esta classe (no nosso caso - "ru.hh.plugins.Ignore" ).
Crie um idioma para arquivos GitIgnore class IgnoreLanguage private constructor() : Language("ru.hh.plugins.Ignore", "ignore", null), InjectableLanguage { companion object { val INSTANCE = IgnoreLanguage() } override fun getDisplayName(): String { return "Ignore() ($id)" } }
Há um recurso aqui: alguns desenvolvedores adicionam uma linha não exclusiva como um identificador. Por esse motivo, a integração do seu plug-in com outros plug-ins pode ser interrompida. Nós somos ótimos, temos uma linha única.
A próxima coisa a fazer depois que criamos o idioma é criar um FileType . Herdamos da classe LanguageFileType , usamos a instância de idioma que definimos para inicializar, substituindo alguns métodos muito simples. Feito. Agora podemos usar o FileType recém-criado.
Crie seu próprio FileType para .gitignore class IgnoreFileType(language: Language) : LanguageFileType(language) { companion object { val INSTANCE = IgnoreFileType(IgnoreLanguage.INSTANCE) } override fun getName(): String = "gitignore file" override fun getDescription(): String = "gitignore files" override fun getDefaultExtension(): String = "gitignore" override fun getIcon(): Icon? = null }
Concluir a criação do arquivo
Depois de encontrar todos os FileType s necessários, recomendo criar um contêiner especial chamado TemplateData - ele conterá todos os dados sobre o modelo do qual você deseja gerar código. Ele conterá o nome do arquivo de modelo, o nome do arquivo de saída obtido após a geração do código, o FileType desejado e , finalmente, o PsiDirectory , onde você adiciona o arquivo criado.
TemplateData data class TemplateData( val templateFileName: String, val outputFileName: String, val outputFileType: FileType, val outputFilePsiDirectory: PsiDirectory? )
Em seguida, retornamos ao FreeMarker - obtemos o arquivo de modelo, usando StringWriter , obtemos o texto; no PsiFileFactory, geramos o PsiFile com o texto e o tipo desejados. O arquivo criado é adicionado ao diretório desejado.
Crie PsiFile na pasta desejada fun createFromTemplate(data: FileTemplateData, properties: Map<String, Any>): PsiFile { val template = freeMarkerConfig.getTemplate(data.templateFileName) val text = StringWriter().use { writer -> template.process(properties, writer) writer.buffer.toString() } return psiFileFactory.createFileFromText(data.outputFileName, data.outputFileType, text) }
Assim, a estrutura PSI é levada em consideração e o IDEA, assim como outros plugins, verá o que fizemos. Pode haver lucro com isso: por exemplo, se o plug-in do Git perceber que você adicionou um novo arquivo, ele exibirá automaticamente uma caixa de diálogo perguntando se você deseja adicionar esses arquivos ao Git?
Conclusões da geração de código
- Arquivos de texto podem ser gerados pelo FreeMarker. Muito confortável
- Ao gerar arquivos, é necessário considerar a estrutura PSI; caso contrário, tudo dará errado.
- Se você deseja gerar arquivos usando o PsiFileFactory, terá que encontrar o FileType em algum lugar.
Bem, agora passamos para a última, a parte prática mais deliciosa - esta é uma modificação do código.
Modificação de código
De fato, criar um plug-in apenas para gerar código não faz sentido, porque você pode gerar código com outras ferramentas e com o mesmo FreeMarker . Mas o que o FreeMarker não pode fazer é modificar o código.
Nossa lista de verificação possui várias tarefas relacionadas à modificação do código, vamos começar pela mais simples - modificar o arquivo settings.gradle .
Modificações settings.gradle
Deixe-me lembrá-lo do que queremos fazer: precisamos adicionar algumas linhas neste arquivo que descreverão o caminho para o módulo recém-criado:
Descrição do caminho para o módulo Assustei um pouco mais cedo que você sempre deve levar em conta a estrutura PSI ao trabalhar com arquivos, caso contrário tudo vai queimar não vai funcionar. De fato, em tarefas simples, como adicionar algumas linhas ao final de um arquivo, você pode fazer isso. Você pode adicionar algumas linhas ao arquivo usando o java.io.File usual. Para fazer isso, localizamos o caminho para o arquivo, criamos a instância java.io.File e, usando as funções de extensão Kotlin, adicionamos duas linhas ao final deste arquivo. Você pode fazer isso, o IDEA verá suas alterações.
Adicionando linhas ao arquivo settings.gradleval projectBaseDirPath = project.basePath ?: return
val settingsPathFile = projectBaseDirPath + "/settings.gradle"
val settingsFile = Arquivo (settingsPathFile)
settingsFile.appendText ("include ': $ moduleName'")
settingsFile.appendText (
"project (': $ moduleName'). projectDir = novo arquivo (settingsDir, '$ folderPath')"
)
Bem, idealmente, é claro, é melhor através da estrutura do PSI - é mais confiável.
Ajuste do Kapt para Palito
Mais uma vez, lembro o problema: no módulo do aplicativo, há um arquivo build.gradle e, dentro dele, há configurações para o processador de anotações. E queremos adicionar um pacote do nosso módulo criado a um local específico.
Nosso objetivo é encontrar um PsiElement específico, após o qual planejamos adicionar nossa linha. A busca pelo elemento começa com a busca pelo PsiFile , que significa o arquivo build.gradle do módulo de aplicativo. E para isso, você precisa encontrar o módulo dentro do qual procuraremos o arquivo.
Estamos à procura de um módulo por nome val appModule = ModuleManager.getInstance(project) .modules.toList() .first { it.name == "headhunter-applicant" }
Em seguida, usando a classe de utilitário FilenameIndex, você pode encontrar PsiFile por seu nome, especificando o módulo encontrado como a área de pesquisa.
Procurando por PsiFile por nome val buildGradlePsiFile = FilenameIndex.getFilesByName( appModule.project, "build.gradle", appModule.moduleContentScope ).first()
Depois de encontrarmos o PsiFile, podemos começar a procurar o PsiElement. , – PSI Viewer . IDEA , PSI- .

- (, build.gradle) , PSI- .

– , PsiFile -.
. PsiFile . .
PsiElement val toothpickRegistryPsiElement = buildGradlePsiFile.originalFile .collectDescendantsOfType<GrAssignmentExpression>() .firstOrNull { it.text.startsWith("arguments") } ?.lastChild ?.children?.firstOrNull { it.text.startsWith("toothpick_registry_children_package_names") } ?.collectDescendantsOfType<GrListOrMap>() ?.first() ?: return
?.. ? PSI-. GrAssignmentExpression , , arguments = [ … ] . , toothpick_registry_children_package_names = [...] , Groovy-.
PsiElement , . . .
PSI- , PsiElementFactory , . Java-? Java-. Groovy? GroovyPsiElementFactory . E assim por diante
PsiElementFactory . Groovy Kotlin , .
PsiElement package name val factory = GroovyPsiElementFactory.getInstance(buildGradlePsiFile.project) val packageName = config.mainParams.packageName val newArgumentItem = factory.createStringLiteralForReference(packageName)
PsiElement .
Map- targetPsiElement.add(newArgumentItem)
kapt- Moxy application
-, , – kapt- Moxy application . : @RegisterMoxyReflectorPackages .
, : PsiFile , PsiElement , … , PsiElement -.
: , @RegisterMoxyReflectorPackages , value , .
, . , PsiManager , PsiClass .
PsiClass @RegisterMoxyReflectorPackages val appModule = ModuleManager.getInstance(project) .modules.toList() .first { it.name == "headhunter-applicant" } val psiManager = PsiManager.getInstance(appModule.project) val annotationPsiClass = ClassUtil.findPsiClass( psiManager, "com.arellomobile.mvp.RegisterMoxyReflectorPackages" ) ?: return
AnnotatedMembersSearch , .
, val annotatedPsiClass = AnnotatedMembersSearch.search( annotationPsiClass, appModule.moduleContentScope ).findAll() ?.firstOrNull() ?: return
, PsiElement , value. , .
val annotationPsiElement = (annotatedPsiClass .annotations .first() as KtLightAnnotationForSourceEntry ).kotlinOrigin val packagesPsiElements = annotationPsiElement .collectDescendantsOfType<KtValueArgumentList>() .first() .collectDescendantsOfType<KtValueArgument>() val updatedPackagesList = packagesPsiElements .mapTo(mutableListOf()) { it.text } .apply { this += "\"${config.packageName}\"" } val newAnnotationValue = updatedPackagesList.joinToString(separator = ",\n")
KtPsiFactory PsiElement – .
val kotlinPsiFactory = KtPsiFactory(project) val newAnnotationPsiElement = kotlinPsiFactory.createAnnotationEntry( "@RegisterMoxyReflectorPackages(\n$newAnnotationValue\n)" ) val replaced = annotationPsiElement.replace(newAnnotationPsiElement)
.
? code style. , IDEA : CodeStyleManager.
code style CodeStyleManager.getInstance(module.project).reformat(replacedElement)
- , .
- , PSI-, .
- , PSI , , PsiElement-.
?
.
- – , .
- . . : .
- ? . , IDEA , . , . — - , GitHub . , , .
- - – IntelliJ IDEA . , Util Manager , , , .
- : . , runIde , IDEA, . , hh.ru, .
Só isso. , , – .
Perguntas frequentes
, . , 2 3 .
, IDEA IDEA SDK , deprecated, , . SDK- , , .
– gitignore . - .
Android Studio Mac OS, Ubuntu, . , Windows, .