Plugin yang fantastis, vol. 2. Berlatih

Di sini Anda dapat membaca artikel pertama dengan teori plug-in engineering.


Dan pada bagian ini saya akan memberi tahu Anda masalah apa yang kami temui selama pembuatan plugin dan bagaimana kami mencoba menyelesaikannya.
gambar


Apa yang akan saya bicarakan?


  • Bagian praktis
    • UI Multipage
    • DI dalam plugin
    • Pembuatan kode
    • Modifikasi kode
  • Apa yang harus dilakukan selanjutnya?
    • Kiat
    • Faq

UI Multipage


Hal pertama yang perlu kami lakukan adalah membuat UI multi-halaman. Kami membuat formulir kompleks pertama dengan sekelompok tanda centang, bidang input. Beberapa saat kemudian, kami memutuskan untuk menambahkan kemampuan untuk memilih daftar modul yang dapat disambungkan pengguna ke modul baru. Dan kami juga ingin memilih modul aplikasi yang kami rencanakan untuk menghubungkan modul yang dibuat.


Memiliki begitu banyak kontrol pada satu formulir sangat tidak nyaman, sehingga mereka membuat tiga halaman terpisah, tiga pemotong cookie terpisah. Singkatnya, dialog Wizard.


gambar


Tetapi karena membuat multi-halaman UI dalam plugin sangat menyakitkan, saya ingin menemukan sesuatu yang siap. Dan di perut IDEA, kami menemukan kelas yang disebut WizardDialog .


gambar


Ini adalah kelas pembungkus di atas dialog reguler, yang secara mandiri memantau kemajuan pengguna dalam panduan, dan menunjukkan tombol yang diperlukan (Sebelumnya, Berikutnya, Selesai, dll.). Sebuah WizardModel khusus dilampirkan ke WizardDialog , di mana WizardSteps ditambahkan. Setiap WizardStep adalah formulir terpisah.


Dalam bentuknya yang paling sederhana, implementasi dialog adalah sebagai berikut:


Wisaya dialog
class MyWizardDialog( model: MyWizardModel, private val onFinishButtonClickedListener: (MyWizardModel) -> Unit ): WizardDialog<MyWizardModel>(true, true, model) { override fun onWizardGoalAchieved() { super.onWizardGoalAchieved() onFinishButtonClickedListener.invoke(myModel) } } 

Kami akan mewarisi dari kelas WizardDialog , parameterisasi dengan kelas WizardModel kami. Kelas ini memiliki panggilan balik khusus ( onWizardGoalAchanted ), yang memberi tahu kami bahwa pengguna melewati wizard hingga akhir dan mengklik tombol "Selesai".
Penting untuk dicatat bahwa dari dalam kelas ini ada peluang untuk hanya menjangkau Model Wizard . Ini berarti bahwa semua data yang akan dikumpulkan pengguna selama berlalunya wizard, Anda harus menambahkan di WizardModel .


Model pemandu
 class MyWizardModel: WizardModel("Title for my wizard") { init { this.add(MyWizardStep1()) this.add(MyWizardStep2()) this.add(MyWizardStep3()) } } 

Modelnya adalah sebagai berikut: kita mewarisi dari kelas WizardModel dan menggunakan metode tambahkan bawaan menambahkan WizardSteps terpisah ke dialog.


Langkah Wizard
 class MyWizardStep1: WizardStep<MyWizardModel>() { private lateinit var contentPanel: JPanel override fun prepare(state: WizardNavigationState?): JComponent { return contentPanel } } 

WizardSteps juga sederhana: kami mewarisi dari kelas WizardStep , mengukurnya dengan kelas model kami, dan, yang paling penting, mendefinisikan kembali metode persiapan, yang mengembalikan komponen root dari formulir masa depan Anda.


Secara sederhana, ini benar-benar terlihat seperti ini. Tetapi di dunia nyata, kemungkinan besar bentuk Anda akan menyerupai sesuatu seperti ini:


gambar


Di sini Anda dapat mengingat saat-saat ketika kita di dunia Android belum tahu apa itu Arsitektur Bersih, MVP dan menulis semua kode dalam satu Kegiatan. Ada bidang baru untuk pertempuran arsitektur, dan jika Anda ingin bingung, Anda dapat menerapkan arsitektur Anda sendiri untuk plugin.


Kesimpulan


Jika Anda memerlukan UI multi-halaman, gunakan WizardDialog - itu akan lebih mudah.


Kami beralih ke topik berikutnya - DI dalam plugin.


DI dalam plugin


Mengapa Injeksi Ketergantungan di dalam plugin diperlukan?
Alasan pertama adalah organisasi arsitektur di dalam plugin.


Tampaknya, mengapa umumnya mengamati beberapa jenis arsitektur di dalam plugin? Plug-in adalah hal yang bermanfaat, begitu saya menulisnya, dan itu saja, saya lupa.
Ya, tapi tidak.
Ketika plugin Anda tumbuh, ketika Anda menulis banyak kode, pertanyaan tentang kode terstruktur muncul dengan sendirinya. Di sini DI mungkin berguna.


Alasan kedua, yang lebih penting - dengan bantuan DI Anda dapat menjangkau komponen yang ditulis oleh pengembang plugin lain. Ini bisa berupa bus acara, penebang dan banyak lagi.


Terlepas dari kenyataan bahwa Anda bebas menggunakan kerangka kerja DI (Spring, Dagger, dll), di dalam IntelliJ IDEA ada kerangka kerja DI Anda sendiri, yang didasarkan pada tiga tingkat abstraksi pertama, yang sudah saya bicarakan: Aplikasi , Proyek dan Modul .


gambar


Masing-masing level ini memiliki abstraksi sendiri yang disebut Komponen . Komponen level yang diperlukan dibuat per instance dari objek level ini. Jadi ApplicationComponent dibuat sekali untuk setiap instance dari kelas Application , mirip dengan ProjectComponent untuk instance Project , dan seterusnya.


Apa yang perlu dilakukan untuk menggunakan kerangka DI?


Pertama, buat kelas yang mengimplementasikan salah satu komponen antarmuka yang diperlukan - misalnya, kelas yang mengimplementasikan ApplicationComponent , atau ProjectComponent , atau ModuleComponent . Pada saat yang sama, kami memiliki kesempatan untuk menyuntikkan objek tingkat yang antarmuka kami laksanakan. Itu, misalnya, di ProjectComponent Anda bisa menyuntikkan objek kelas Project .


Membuat kelas komponen
 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 

Kedua, dimungkinkan untuk menyuntikkan komponen lain pada level yang sama atau lebih tinggi. Misalnya, di ProjectComponent Anda dapat menyuntikkan ProjectComponent atau ApplicationComponent lainnya . Di sinilah Anda dapat mengakses instance komponen "alien".


Pada saat yang sama, IDEA menjamin bahwa seluruh grafik dependensi akan dirakit dengan benar, semua objek akan dibuat dalam urutan yang benar dan diinisialisasi dengan benar.


Hal selanjutnya yang harus dilakukan adalah mendaftarkan komponen dalam file plugin.xml . Segera setelah Anda mengimplementasikan salah satu antarmuka Komponen (misalnya, ApplicationComponent ), IDEA akan segera menawarkan untuk mendaftarkan komponen Anda di plugin.xml.


Daftarkan komponen di 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> 

Bagaimana ini dilakukan? Tag <project-component> khusus muncul ( <aplikasi-komponen> , <module-komponen> - tergantung level). Ada sebuah tag di dalamnya , ia memiliki dua tag lagi: <interface-class> , di mana nama antarmuka komponen Anda ditunjukkan, dan <implementation-class> , di mana kelas implementasi diindikasikan. Satu dan kelas yang sama dapat berupa antarmuka komponen atau implementasinya, sehingga Anda dapat melakukannya dengan satu tag <implementation-class> .

Hal terakhir yang harus dilakukan adalah mendapatkan komponen dari objek yang sesuai, yaitu, kita mendapatkan ApplicationComponent dari instance Application , ProjectComponent dari Project , dll.


Dapatkan komponennya
 val myAppComponent = application.getComponent(MyAppComponent::class.java) val myProjectComponent = project.getComponent(MyProjectComponent::class.java) val myModuleComponent = module.getComponent(MyModuleComponent::class.java) 

Kesimpulan


  1. Ada kerangka kerja DI di dalam IDEA - tidak perlu menyeret apa pun pada Anda sendiri: baik Dagger maupun Spring. Meskipun tentu saja bisa.
  2. Dengan DI ini, Anda dapat menjangkau komponen yang sudah jadi, dan ini adalah jus itu sendiri.

Mari kita beralih ke tugas ketiga - pembuatan kode.


Pembuatan kode


Ingat, dalam daftar periksa kami memiliki tugas menghasilkan banyak file? Setiap kali kita membuat modul baru, kita membuat banyak file: interaksor, penyaji, fragmen. Saat membuat modul baru, komponen-komponen ini sangat mirip satu sama lain, dan saya ingin belajar cara membuat kerangka kerja ini secara otomatis.


Pola


Apa cara termudah untuk menghasilkan satu ton kode serupa? Gunakan pola. Pertama, Anda perlu melihat templat Anda dan memahami persyaratan apa yang diajukan untuk pembuat kode.


Sepotong templat file 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">') }> ... } 

Pertama: kami ingin dapat menggunakan kondisi di dalam pola ini. Saya memberi contoh: jika plugin entah bagaimana terhubung dengan UI, kami ingin menghubungkan Gradle-plugin khusus kotlin-android-extensions .


Kondisi di dalam templat
 <if (isKotlinProject) { apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' <if (isModuleWithUI) { apply plugin: 'kotlin-android-extensions' }> }> 

Hal kedua yang kita inginkan adalah kemampuan untuk menggunakan variabel di dalam template ini. Misalnya, ketika kami mengonfigurasi kapt untuk Moxy, kami ingin menyisipkan nama paket sebagai argumen ke prosesor anotasi.


Ganti nilai variabel di dalam templat
 kapt { arguments { arg("moxyReflectorPackage", '<include var="packageName">') } } 

Hal lain yang kita butuhkan adalah kemampuan untuk menangani loop di dalam template. Ingat formulir tempat kami memilih daftar modul yang ingin kami sambungkan ke modul baru yang sedang dibuat? Kami ingin mengelilingi mereka dalam satu lingkaran dan menambahkan baris yang sama.


Gunakan loop dalam template.
 <for (moduleName in enabledModules) { compileOnly project('<include var="moduleName">') }> 

Jadi, kami mengajukan tiga syarat untuk pembuat kode:


  • Kami ingin menggunakan kondisinya
  • Kemampuan untuk mengganti nilai variabel
  • Kita membutuhkan loop dalam pola

Pembuat kode


Apa saja opsi untuk mengimplementasikan pembuat kode? Anda dapat, misalnya, menulis pembuat kode Anda sendiri. Misalnya, orang-orang dari Uber melakukan ini: mereka menulis plug-in mereka sendiri untuk menghasilkan riblets (yang disebut unit arsitektur). Mereka datang dengan bahasa templat mereka sendiri , di mana mereka hanya menggunakan kemampuan untuk memasukkan variabel. Mereka membawa kondisi ke tingkat generator . Tetapi kami berpikir bahwa kami tidak akan melakukan itu.


Opsi kedua adalah menggunakan kelas utilitas FileTemplateManager yang dibangun di IDEA, tetapi saya tidak akan merekomendasikan melakukan ini. Karena dia memiliki Velocity sebagai mesin, yang memiliki beberapa masalah dengan meneruskan objek Java ke template. Selain itu, FileTemplateManager tidak dapat menghasilkan file selain Java atau XML dari kotak. Dan kami perlu membuat file Groovy, Kotlin, Proguard, dan jenis file lainnya.


Opsi ketiga adalah ... FreeMarker . Jika Anda memiliki template FreeMarker yang sudah jadi , jangan buru-buru membuangnya - mereka dapat berguna bagi Anda di dalam plugin.


Apa yang perlu dilakukan, apa yang harus digunakan FreeMarker di dalam plugin? Pertama, tambahkan templat file. Anda dapat membuat folder / templat di dalam folder / resources dan menambahkan semua templat kami di sana untuk semua file - presenter, fragmen, dll.


gambar


Setelah itu, Anda perlu menambahkan ketergantungan pada perpustakaan FreeMarker. Karena plugin menggunakan Gradle, menambahkan dependensi sangatlah mudah.


Tambahkan ketergantungan pada perpustakaan FreeMarker
 dependencies { ... compile 'org.freemarker:freemarker:2.3.28' } 

Setelah itu, konfigurasikan FreeMarker di dalam plugin kami. Saya menyarankan Anda untuk hanya menyalin konfigurasi ini di sini - itu disiksa, diderita, salin, dan semuanya berfungsi.


Konfigurasi 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 } } ... 

Saatnya membuat file menggunakan FreeMarker . Untuk melakukan ini, kita mendapatkan templat dari konfigurasi berdasarkan namanya dan menggunakan FileWriter biasa membuat file dengan teks yang diinginkan langsung pada disk.


Membuat file melalui 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) } } } 

Dan tugas itu tampaknya diselesaikan, tetapi tidak. Pada bagian teoretis, saya menyebutkan bahwa seluruh IDEA diserap dengan struktur PSI, dan ini harus diperhitungkan. Jika Anda membuat file yang melewati struktur PSI (misalnya, melalui FileWriter), maka IDEA tidak akan mengerti bahwa Anda membuat sesuatu dan tidak akan menampilkan file di pohon proyek. Kami menunggu sekitar tujuh menit sebelum IDEA diindeks dan melihat file yang dibuat.


Kesimpulan - lakukan dengan benar, buat file, dengan mempertimbangkan struktur PSI.


Buat struktur PSI untuk file


Untuk memulai, mari kita lihat struktur folder menggunakan PsiDirectory . Direktori awal proyek dapat diperoleh dengan menggunakan fungsi ekstensi guessProjectDir dan toPsiDirectory :


Dapatkan Proyek PsiDirectory
 val projectPsiDirectory = project.guessProjectDir()?.toPsiDirectory(project) 

Direktori selanjutnya dapat ditemukan menggunakan metode kelas PsiDirectory findSubdirectory , atau dibuat menggunakan metode createSubdirectory .


Temukan dan buat PsiDirectory
 val coreModuleDir = projectPsiDirectory.findSubdirectory("core") val newModulePsiDir = coreModuleDir.createSubdirectory(config.mainParams.moduleName) 

Saya juga menyarankan Anda membuat Peta dari mana Anda bisa mendapatkan semua struktur folder PsiDirectory menggunakan kunci string, dan kemudian menambahkan file yang dibuat ke salah satu folder ini.


Buat peta struktur folder

return mutableMapOf <String, PsiDirectory?> (). apply {
ini ["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") // ... 

}


Folder dibuat. Kami akan membuat PsiFiles menggunakan PsiFileFactory . Kelas ini memiliki metode khusus yang disebut createFileFromText . Metode menerima tiga parameter sebagai input: nama (String fileName), teks (String text) dan ketik (FileType fileType) dari file output. Dua dari tiga parameter jelas di mana mendapatkannya: kita tahu namanya sendiri, kita mendapatkan teks dari FreeMarker. Dan dari mana mendapatkan FileType ? Dan tentang apa semua ini?


Jenis file


FileType adalah kelas khusus yang menunjukkan jenis file. Hanya dua FileType yang tersedia bagi kita dari "kotak": JavaFileType dan XmlFileType, masing-masing untuk file Java dan file XML. Tetapi muncul pertanyaan: di mana mendapatkan jenis untuk file build.gradle , untuk file Kotlin , untuk Proguard , untuk .gitignore , akhirnya?!


Pertama, sebagian besar FileType ini dapat diambil dari plugin lain yang sudah ditulis oleh seseorang. GroovyFileType dapat diambil dari plugin Groovy , KotlinFileType dari plugin Kotlin , Proguard dari plugin Android .


Bagaimana kita menambahkan ketergantungan plugin lain ke plugin kita? Kami menggunakan gradle-intellij-plugin . Ia menambahkan blok intellij khusus ke file build.gradle dari plugin, di dalamnya terdapat properti khusus - plugin . Di properti ini, Anda dapat mencantumkan daftar pengidentifikasi plugin yang ingin kami andalkan.


Tambahkan dependensi pada plugin lain
 // build.gradle  intellij { … plugins = ['android', 'Groovy', 'kotlin'] } 

Kami mengambil kunci dari repositori plugin JetBrains resmi. Untuk plugin yang dibangun di IDEA (yaitu Groovy, Kotlin, dan Android), nama folder plugin di dalam IDEA sudah cukup. Untuk selebihnya, Anda perlu pergi ke halaman plugin tertentu di repositori plugin JetBrains resmi, properti XML ID Plugin akan ditunjukkan di sana, serta versi (misalnya, di sini adalah halaman dari plugin Docker ). Baca lebih lanjut tentang menghubungkan plugin lain di GitHub .


Kedua, Anda perlu menambahkan deskripsi ketergantungan ke file plugin.xml . Ini dilakukan menggunakan tag .

Kami menghubungkan plugin di plugin.xml
 <idea-plugin> ... <depends>org.jetbrains.android</depends> <depends>org.jetbrains.kotlin</depends> <depends>org.intellij.groovy</depends> </idea-plugin> 

Setelah kami menyinkronkan proyek, kami akan memperketat dependensi dari plugin lain, dan kami akan dapat menggunakannya.


Tetapi bagaimana jika kita tidak ingin bergantung pada plugin lain? Dalam hal ini, kita dapat membuat tulisan rintisan untuk jenis file yang kita butuhkan. Untuk melakukan ini, pertama buat kelas yang akan mewarisi dari kelas Bahasa . Pengidentifikasi unik bahasa pemrograman kami akan diteruskan ke kelas ini (dalam kasus kami - "ru.hh.plugins.Ignore" ).


Buat bahasa untuk file 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)" } } 

Ada fitur di sini: beberapa pengembang menambahkan garis non-unik sebagai pengidentifikasi. Karena itu, integrasi plugin Anda dengan plugin lain mungkin rusak. Kami hebat, kami memiliki garis yang unik.


Hal selanjutnya yang harus dilakukan setelah kami membuat Bahasa adalah membuat FileType . Kami mewarisi dari kelas LanguageFileType , menggunakan instance bahasa yang kami definisikan untuk menginisialisasi, menimpa beberapa metode yang sangat sederhana. Selesai Sekarang kita bisa menggunakan FileType yang baru dibuat.


Buat FileType Anda sendiri untuk .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 } 

Selesai membuat file


Setelah Anda menemukan semua FileType yang diperlukan, saya sarankan membuat wadah khusus yang disebut TemplateData - itu akan berisi semua data tentang templat dari mana Anda ingin membuat kode. Ini akan berisi nama file template, nama file output yang Anda dapatkan setelah membuat kode, FileType yang diinginkan , dan akhirnya PsiDirectory , tempat Anda menambahkan file yang dibuat.


TemplateData
 data class TemplateData( val templateFileName: String, val outputFileName: String, val outputFileType: FileType, val outputFilePsiDirectory: PsiDirectory? ) 

Kemudian kita kembali ke FreeMarker - kita mendapatkan file templat darinya, menggunakan StringWriter kita mendapatkan teks, di PsiFileFactory kita menghasilkan PsiFile dengan teks dan jenis yang diinginkan. File yang dibuat ditambahkan ke direktori yang diinginkan.


Buat PsiFile di folder yang diinginkan
 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) } 

Dengan demikian, struktur PSI diperhitungkan, dan IDEA, serta plugin lainnya, akan melihat apa yang telah kami lakukan. Mungkin ada untung dari ini: misalnya, jika plugin untuk Git melihat bahwa Anda telah menambahkan file baru, itu akan secara otomatis menampilkan kotak dialog yang menanyakan apakah Anda ingin menambahkan file-file ini ke Git?


Kesimpulan Pembuatan Kode


  • File teks dapat dihasilkan oleh FreeMarker. Sangat nyaman
  • Saat membuat file, Anda perlu mempertimbangkan struktur PSI, jika tidak semuanya akan salah.
  • Jika Anda ingin membuat file menggunakan PsiFileFactory, Anda harus menemukan FileType di suatu tempat.

Nah, sekarang kita beralih ke bagian terakhir, bagian praktis paling enak - ini adalah modifikasi kodenya.


Modifikasi kode


Sebenarnya, membuat plugin hanya untuk menghasilkan kode adalah omong kosong, karena Anda dapat menghasilkan kode dengan alat lain, dan FreeMarker yang sama. Tapi yang tidak bisa dilakukan FreeMarker adalah memodifikasi kodenya.


Daftar periksa kami memiliki beberapa tugas yang berkaitan dengan memodifikasi kode, mari kita mulai dengan yang paling sederhana - memodifikasi file settings.gradle .


Pengaturan modifikasi .gradle


Biarkan saya mengingatkan Anda apa yang ingin kami lakukan: kami perlu menambahkan beberapa baris ke file ini yang akan menjelaskan jalur ke modul yang baru dibuat:


Deskripsi jalur ke modul
 // settings.gradle include ':analytics project(':analytics').projectDir = new File(settingsDir, 'core/framework-metrics/analytics) ... include ':feature-worknear' project(':feature-worknear').projectDir = new File(settingsDir, 'feature/feature-worknear') 

Saya takut Anda sedikit sebelumnya bahwa Anda harus selalu mempertimbangkan struktur PSI ketika bekerja dengan file, jika tidak semuanya akan terbakar tidak akan bekerja Bahkan, dalam tugas-tugas sederhana, seperti menambahkan beberapa baris ke akhir file, ini bisa dihilangkan. Anda dapat menambahkan beberapa baris ke file menggunakan java.io.File biasa. Untuk melakukan ini, kami menemukan path ke file, membuat contoh java.io.File , dan menggunakan fungsi ekstensi Kotlin menambahkan dua baris ke akhir file ini. Anda dapat melakukan ini, IDEA akan melihat perubahan Anda.


Menambahkan baris ke file settings.gradle

val projectBaseDirPath = project.basePath ?: return
val settingsPathFile = projectBaseDirPath + "/settings.gradle"


val settingsFile = File (settingsPathFile)


settingsFile.appendText ("include ': $ moduleName'")
settingsFile.appendText (
"project (': $ moduleName'). projectDir = File baru (settingsDir, '$ folderPath')"
)


Yah, idealnya, tentu saja, lebih baik melalui struktur PSI - lebih dapat diandalkan.


Kapt tala untuk Tusuk Gigi


Sekali lagi, saya mengingatkan Anda tentang masalah: di modul aplikasi ada file build.gradle , dan di dalamnya ada pengaturan prosesor penjelasan. Dan kami ingin menambahkan paket modul yang kami buat ke tempat tertentu.


Kemana kemana?

gambar


Tujuan kami adalah untuk menemukan PsiElement tertentu, setelah itu kami berencana untuk menambahkan baris kami. Pencarian elemen dimulai dengan pencarian PsiFile , yang menunjukkan file build.gradle dari modul aplikasi. Dan untuk ini, Anda perlu menemukan modul di dalamnya kita akan mencari file.


Kami mencari modul dengan nama
 val appModule = ModuleManager.getInstance(project) .modules.toList() .first { it.name == "headhunter-applicant" } 

Selanjutnya, menggunakan kelas utilitas FilenameIndex, Anda dapat menemukan PsiFile dengan namanya, menentukan modul yang ditemukan sebagai area pencarian.


Mencari PsiFile dengan Nama
 val buildGradlePsiFile = FilenameIndex.getFilesByName( appModule.project, "build.gradle", appModule.moduleContentScope ).first() 

Setelah kami menemukan PsiFile, kami dapat mulai mencari PsiElement. , – PSI Viewer . IDEA , PSI- .


gambar


- (, build.gradle) , PSI- .


gambar


– , 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 . Dan sebagainya.


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 .


-?

gambar


, : 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) 

- , .


Kesimpulan


  • , PSI-, .
  • , PSI , , PsiElement-.

?


.


  • – , .
  • . . : .
  • ? . , IDEA , . , . — - , GitHub . , , .
  • - – IntelliJ IDEA . , Util Manager , , , .
  • : . , runIde , IDEA, . , hh.ru, .

Itu saja. , , – .


Faq


  • ?

, . , 2 3 .


  • IDEA , - ?

, IDEA IDEA SDK , deprecated, , . SDK- , , .


  • ?

– gitignore . - .


  • ?

Android Studio Mac OS, Ubuntu, . , Windows, .

Source: https://habr.com/ru/post/id463611/


All Articles