通过编写插件个性化你的IntelliJ IDEA

还记得2013年半夜观看Google
I/O大会,一堆眼花缭乱的数字广告后,主持人提到这次I/O的主角是开发者,接下来给大家演示一系列开发工具和服务时,心想:可能无非就是一些向开发者开放的Google的API,这些对于一个从事Eclipse插件开发者来讲,确实遥远。果不其然,Maps
API,Google Now API,悉数登场,接下来Google Play Games的游戏服务登场,其服务下的游戏和主机平台下的游戏,甚至与掌机平台下的游戏比简直就是小儿科,且演示多人实时游戏时,现场也没成功,实在是让人有洗洗睡了的冲动,就在这时,另一位主持人上场,Android
Studio的图片突然出现在大屏幕上,还未来的及反应,黑色主题的开发环境映入眼帘,项目结构树,包含大段的代码编辑器,难道是Eclipse ADT Plugin,再仔细看,噢,IntelliJ
IDEA,此时主持人的一连串操作,现场已是一篇欢呼,酷酷的应用可视化开发,短短的4分钟时间,已经让我心潮澎湃,一直以来想细致了解Idea
ide插件开发的冲动,顺时被激发了,因为从9.0开源的时候,那份第一时间下载的源码一直没有细致的去了解过,因为一直忙于Eclipse插件的开发,所以4年之后的今天,再次拾起当年写的一个实例插件,再去找当前的中文插件开发论坛,大多都成了使用论坛,唯一的一份中文资料也还是多年前的,再次来到官方wiki,IntelliJIDEA
Plugin Development主题下的内容足已让人感叹,中文社区的idea ide插件的开发者是多么的稀少,显然你可以用Intellij旗下的各类产品写出高质量的java,js,python,ruby,甚至objective-c代码产品,但开发一个成熟的IntelliJ
IDEA插件对我来说却是一件很着迷的事情。

那么IntelliJ IDEA插件开发又是如何实现的呢,IDEA
IDE和Eclipse插件开发开发又有什么不同呢?Eclipse插件开发中已经开发好的插件代码是否可以复用呢?等等问题放在面前需要去探索发现,但相关技术信息获取则已官方WIKI及相关论坛板块为主,而国内开发者又多已使用工具为主,但对工具本身实现却极少关注,但随着9.0的开源,其实代码开放已为广大开发者提供了最好的技术细节。本文就已自身学习实践通过解读源码来分享一些相关技术信息,以便为今后进一步完善插件打好坚实的基础,可以回顾一下官方的一篇Alexey
Efimov编写的IntelliJ IDEA Plugin Development,其中有提到插件是如何工作的一节中关于IntelliJ
IDEA component模型的描述,原文中这样写到:IntelliJIDEA包含多种组件模型,这些组件模型都是基于PicoContainer的,大家应该应该对PicoContainer容器并不陌生,但插件又是如何基于PicoContainer来工作的呢?还是从最佳途径源代码入手来进行理解。

但本文对插件又是如何基于PicoContainer来完成管理和加载不做详细描述,仅仅在应用层面完成一个改变IntelliJ
IDEA界面图标样式的小插件

,为自己打造一个个性化的IDE开发工具,我们都知道IDEAide是纯正的java
swing项目,swing作为传统的跨平台客户端用户界面组件,可定制行

强,用户可通过编写代码就可定制诸如边框,颜色,背景,透明度等属性,完成高度自定义的视觉效果,这次我们利用swing这一特点定制IntelliJ
IDEA

界面图标样式完成一个个性化的小插件,而不仅仅停留在开发工具使用和配置这一肤浅的层面。先看下效果图:

显示图标全部采用字体矢量图标,达到高质量显示效果

图标是如何被渲染绘制在工具栏或者菜单上呢,随着9.0版本的IDEA
IDE社区版的OpenAPI发布,代码开放已为广大开发者提供了最好的技术细节,

通过查看代码,IDEA IDE的图标资源均是通过com.intellij.icons.AllIcons进行统一管理的,图标资源均为png格式,

那么我们能不能用矢量图标来进行显示呢?Swing组件是否支持矢量图标的渲染绘制?答案是肯定的,那么实现思路就可以确定了。

1.根据AllIcons类中关于图标对应的对象均为javax.swing.Icon接口对象,那么构件一个我们自己实现了Icon接口的构件矢量图标对象,

通过覆写ImageIcon中的paintIcon方法来完成我们自己的图标对象是一件很简单的事情,具体写法如下:

class FontIcon(font: Font, iconFont: IIconFont) extends ImageIcon {

  setSize(14)

  var size: Integer = _
  var iconWidth: Integer = _
  var iconHeight: Integer = _
  var deriveFont: Font = _

  def setSize(size: Integer) = {
    if (size > 0) {
      this.size = size
      deriveFont = font.deriveFont(Font.PLAIN, size.floatValue())
      val bufferImage: BufferedImage = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB)
      val graphics2d: Graphics2D = GraphicsEnvironment.getLocalGraphicsEnvironment.createGraphics(bufferImage)
      graphics2d.setFont(deriveFont)
      this.iconWidth = graphics2d.getFontMetrics.charWidth(iconFont.getCode())
      this.iconHeight = graphics2d.getFontMetrics.getHeight
      graphics2d.dispose()
    }
  }

  override def paintIcon(c: Component, g: Graphics, x: Int, y: Int): Unit = {
    val bufferedImage = new BufferedImage(getIconWidth, getIconHeight, BufferedImage.TYPE_INT_ARGB)
    val graphics2d = bufferedImage.getGraphics.asInstanceOf[Graphics2D]
    graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB)
    graphics2d.setFont(deriveFont)
    if (!UIUtil.isUnderDarcula)
      graphics2d.setColor(Color.DARK_GRAY)
    else
      graphics2d.setColor(Color.LIGHT_GRAY)
    graphics2d.drawString(String.valueOf(iconFont.getCode), 0, graphics2d.getFontMetrics.getAscent)
    graphics2d.dispose();
    g.drawImage(bufferedImage, x, y, null)
  }

  override def getIconHeight: Int = {
    return iconHeight
  }

  override def getIconWidth: Int = {
    return iconWidth
  }
}

2.通过编写ApplicationComponent完成对AllIcons中所有的图标对象filed通过反射API完成对象变更,变更结果则是将AllIcons中所有的Icon

对象均指向我们自型构建的矢量图标对象,那么引用这些图标的Swing组件也会呈现我们的自己图标了,代码如下:

class FontIconComponent extends ApplicationComponent {

  val CACHED_IMAGE_ICON: String = "$CachedImageIcon"

  val MY_URL: String = "myUrl"

  val MY_REAL_ICON: String = "myRealIcon"

  val ALL_ICONS: String = "com.intellij.icons.AllIcons"

  var iconFontList: java.util.List[IIconFonts] = new java.util.ArrayList[IIconFonts]()

  override def initComponent(): Unit = {
    registerFonts
    val ideaIconClass: Class[_] = Class.forName(ALL_ICONS)
    val classes: Array[Class[_]] = ideaIconClass.getDeclaredClasses
    classes.foreach(clazz => fixIcon(clazz))
  }

  def registerFonts: Unit = {
    val iconFontBeans = Extensions.getExtensions(IconFontEP.ICON_EP_NAME)
    iconFontBeans.foreach(iconFontBean => initFont(iconFontBean))
  }

  def initFont(iconFontEP: IconFontEP) = {
    try {
      val ttf = iconFontEP.getTtf
      val stream = iconFontEP.getFontClass.getResourceAsStream(ttf)
      val font = Font.createFont(Font.TRUETYPE_FONT, stream)
      if (GraphicsEnvironment.getLocalGraphicsEnvironment.registerFont(font)) {
        val iconFonts = iconFontEP.getIConFonts
        iconFonts.setFont(font)
        iconFontList.add(iconFonts)
      }
      stream.close()
    } catch {
      case ex: Exception => ex.printStackTrace()
    }
  }

  def fixIcon(clazz: Class[_]): Unit = {
    val fields: Array[Field] = clazz.getDeclaredFields
    fields.foreach(field => fixIcon(field))
    val classes: Array[Class[_]] = clazz.getDeclaredClasses
    classes.foreach(clazz => fixIcon(clazz))
  }

  def fixIcon(field: Field) = {
    try {
      if (Modifier.isStatic(field.getModifiers)) {
        //--静态属性信息可通过null参数获取对象实例,非静态属性需要通过传入类对象获取实例
        val icon = field.get(null)
        val iconClass = icon.getClass

        if (iconClass.getName.endsWith(CACHED_IMAGE_ICON)) {
          val fieldName = field.getName
          val iconFontArray = iconFontList.toArray
          iconFontArray.foreach(iconFonts => if (iconFonts.asInstanceOf[IIconFonts].getIConFont(fieldName) != null) patchFields(icon, iconFonts.asInstanceOf[IIconFonts].getFont, iconFonts.asInstanceOf[IIconFonts].getIConFont(fieldName)))
        }
      }

    } catch {
      case e: Exception => e.printStackTrace()
    }

  }

  def patchFields(obj: Object, font: Font, iconFont: IIconFont) = {
    try {
      val clazz = obj.getClass
      val realIconField = clazz.getDeclaredField(MY_REAL_ICON)
      val darkField = clazz.getDeclaredField("dark")

      val fontAwesomeIcon = new FontIcon(font, iconFont)
      realIconField.setAccessible(true)
      realIconField.set(obj, fontAwesomeIcon)
      if (UIUtil.isUnderDarcula) {
        darkField.setAccessible(true)
        darkField.set(obj, true)
      }
    } catch {
      case ex: Exception => ex.printStackTrace()
    }

  }

  override def disposeComponent(): Unit = {

  }

  override def getComponentName: String = {
    return "FontIconComponent"
  }

哈哈,这样我们就完成一个个性化小插件的主要逻辑的开发。是不是很简单。

到这里是不是就结束了呢?其实并没有,从插件的功能上我们确实已完成我们初期的愿景,但对于一个合格开发人员来讲,不光要实现功能,应该为其他开发人员提供友好的接口(SDK),能够通过SDK让更多的开发人员投身当中,不断丰富功能,回到当前插件当中,由于插件中的图标本是按个人喜好来完成定义的,而且更改的图标并不是全部,那么如何通过SDK让更多的开发人员完成图标定义或者充实其他未修改图标呢?我们可以通过IntelliJ
IDEA自定义扩展点方式完成扩展开发,代码如下:

<extensionPoints>
    <extensionPoint name="iconFont" beanClass="cn.robin.vectorIconPack.api.IconFontEP"></extensionPoint>
  </extensionPoints>

  <extensions defaultExtensionNs="com.intellij">
    <!-- Add your extensions here -->
  </extensions>

  <extensions defaultExtensionNs="cn.robin.vectorIconPack">
    <iconFont id="iconFont" implementClass="cn.robin.vectorIconPack.iconFont.IconFonts" ttf="iconfont.ttf"></iconFont>
    <iconFont id="fontAwesome" implementClass="cn.robin.vectorIconPack.awesomeFont.AwesomeFonts"
              ttf="fontawesome-webfont.ttf"></iconFont>
  </extensions>

通过自己编写的SDK完成对自定义扩展点的使用。

到此为止插件功能开发及与之匹配的SDK开发均已完成,还不快去找自己喜欢的矢量图标。

最后,陈述一些看法,ideaide做为现阶段比较流行的IDE,在国内基本均是已使用,配置为主,但其实作为工具类这一基础软件,在IDEA
IDE社区版的发布,代码开放这一给力的条件下,做为开发人员应更深入了解其本身的运行原理,学习其先进理念,为基础软件开发积累经验,本人当前从事Eclipse插件开发,其中使用的EMF相关技术亦可与IDEAIDE插件开发进行结合,希望有机会和大家一起分享。谢谢

相关代码:https://github.com/OuYuBin/VectorIcons

时间: 2024-10-02 11:48:07

通过编写插件个性化你的IntelliJ IDEA的相关文章

Qt中如何 编写插件 加载插件 卸载插件

Qt中如何 编写插件 加载插件 卸载插件是本文要介绍的内容.Qt提供了一个类QPluginLoader来加载静态库和动态库,在Qt中,Qt把动态库和静态库都看成是一个插件,使用QPluginLoader来加载和卸载这些库.由于在开发项目的过程中,要开发一套插件系统,就使用了Qt的这套类库. 一 编写插件 编写一个Qt的插件需要以下步骤 1.声明一个插件类, 2.定义一个类,实现这个插件类定义的接口,定义的这个类必须从QObject集成下来. 3.使用Q_INTERFACESQ_INTERFACE

jquery编写插件的方法

版权声明:作者原创,转载请注明出处! 编写插件的两种方式: 1.类级别开发插件(1%) 2.对象级别开发(99%) 类级别的静态开发就是给jquery添加静态方法,三种方式 1.添加新的全局函数 2.使用$.extend(obj) 3.使用命名空间 类级别开发插件(用的非常少,1%) 分别举例: //1.直接给jquer添加全局函数 jQuery.myAlert=function (str) { alert(str); }; //2.用extend()方法.extend是jquery提供的一个方

自己编写插件 之 无缝滚动

昨天 - 扒衣见君节,今天 - 牛郎织女节.光棍只能来撸代码了,哎! 首先来看下html骨架,如下: <div class="box"> <ul> <li>111</li> <li>222</li> <li>333</li> </ul> </div> 结构简单明了,没什么说的. 讲下实现原理: div box是最外层盒子,给它指定的宽高,记得给box添加一个 over

jQuery自己编写插件()

引言: 在项目中不同页面经常要用到已经写好的交互,比如弹窗,比如下拉菜单,比如选项卡,比如删除... 此时如果每次都把代码copy一份无疑是一件比较麻烦并且无趣的事情,而且个人认为有些low了,我们可是要追寻 高大上的90后有为青年呢~可是该如何高大上呢?这时jQuery自定义插件开发来了,第一次听到插件开发觉得如此happy, 遂动手网上查找资料进行学习,如下,我用自己的语言将插件开发的程序步骤写出来,如有错误,欢迎指正. 1:jQuery插件开发分为类级别开发和对象级别开发,因为类级别开发在

Emmet:HTML/CSS编写插件

http://www.iteye.com/news/27580 用法: http://docs.emmet.io/cheat-sheet/ sublime 2 添加:1. Ctrl+Alt+p -> install 2. emmet Emmet:HTML/CSS编写插件

Firefox 及其 插件“个性化设置”备份

2016.11.08 重新使用 Firefox(版本为 49.0.2),并设置为 默认浏览器 常用插件有:Tab Mix Plus.QuickDrag.Fast Dial Firefox个性化设置 1.开发者工具 插件个性化设置 1.QuickDrag 2.Fast Dial 3.Tab Mix Plus (在 地址栏 右侧,拖放 “已关闭的标签页” 按钮) (标签页 右键菜单,是重点)

再谈:jquery编写插件的方法

版权声明:作者原创,转载请注明出处! 编写插件的两种方式: 1.类级别开发插件(1%) 2.对象级别开发(99%) 类级别的静态开发就是给jquery添加静态方法,三种方式 1.添加新的全局函数 2.使用$.extend(obj) 3.使用命名空间 类级别开发插件(用的非常少,1%) 分别举例: //1.直接给jquer添加全局函数 jQuery.myAlert=function (str) { alert(str); }; //2.用extend()方法.extend是jquery提供的一个方

jquery编写插件(转)

阅读目录 基本方法 支持链式调用 让插件接收参数 面向对象的插件开发 关于命名空间 关于变量定义及命名 压缩的好处 工具 GitHub Service Hook 原文:http://www.cnblogs.com/Wayou/p/jquery_plugin_tutorial.html 要说jQuery 最成功的地方,我认为是它的可扩展性吸引了众多开发者为其开发插件,从而建立起了一个生态系统.这好比大公司们争相做平台一样,得平台者得天下.苹果,微软,谷歌等巨头,都有各自的平台及生态圈. 学会使用j

burpsuite编写插件--环境安装

Burpsuite编写插件环境搭建 在安全测试过程中,我们经常会使用到burpsuite,在burp中允许我们自己编写插件,在Extender-BApp Store中可以选择我们要安装的插件: 但是有时候找不到我们需要的插件,这时候需要我们自己编写,burp支持使用python.java.ruby三种语言.下面我来讲解一下如何使用python编写burpsuite插件.(我是在windows下操作的,其他操作系统相似) 1. 使用python编写插件,我们需要导入jython standalon