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

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


那么IntelliJ IDEA插件开发又是如何实现的呢,IDEA IDEEclipse插件开发开发又有什么不同呢?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自定义扩展点方式完成扩展开发,代码如下:


    
  

  
    
  

  
    
    
  

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


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


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


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



你可能感兴趣的:(通过编写插件个性化你的IntelliJ IDEA)