Plugin Content

插件的分发将使用Gradle或Plugin DevKit构建。

插件的.jar文件必须包含以下内容:

配置文件(META-INF/plugin.xml)(插件配置文件)

实现插件功能的类

推荐:插件标志文件(META-INF/pluginIcon*.svg)(插件标志)

请参阅有关优化插件分发文件的重要步骤的“分发大小”。

无法将插件分发定位到特定操作系统(问题)。

无依赖的插件

一个由单个.jar文件组成的插件被放置在/plugins目录中。

.IntelliJIDEAx0/
└── plugins
    └── sample.jar
        ├── com/company/sample/SamplePluginService.class
        │   ...
        │   ...
        └── META-INF
            ├── plugin.xml
            ├── pluginIcon.svg
            └── pluginIcon_dark.svg

带有依赖项的插件

插件的.jar文件与所有所需的捆绑库一起放置在插件的“根”文件夹下的/lib文件夹中。

/lib文件夹中的所有.jar文件都会自动添加到类路径中(也请参阅插件类加载器)。

.IntelliJIDEAx0/
└── plugins
    └── sample
        └── lib
            ├── lib_foo.jar
            ├── lib_bar.jar
            │   ...
            │   ...
            └── sample.jar
                ├── com/company/sample/SamplePluginService.class
                │   ...
                │   ...
                └── META-INF
                    ├── plugin.xml
                    ├── pluginIcon.svg
                    └── pluginIcon_dark.svg

Bundling Plugin API Sources

如果一个插件公开自己的API供其他插件使用,那么考虑将插件的API源码捆绑在ZIP分发中是值得的。

如果第三方插件使用Gradle IntelliJ插件,并添加了一个依赖于将源码捆绑在ZIP分发中的插件,那么源码将自动附加到插件库中,并且在开发人员导航到API类时在IDE中可见。

能够看到API源码极大地改善了开发体验,强烈建议将它们捆绑在一起。

API源码的位置

API源码的JAR文件必须位于插件ZIP分发中的 example-plugin.zip!/plugin/lib/src 目录中,例如:

example-plugin.zip
└── example-plugin
    └── lib
        ├── example-plugin.jar
        └── src
            └── example-plugin-api-src.jar

插件ZIP可以包含多个源码JAR文件,并且对于源码JAR的命名没有严格的规定。

定义插件 API

通常,以下类被视为插件API:

扩展点及相关类

监听器及相关类

提供对插件数据/行为访问的服务和实用工具

请记住,API应该是稳定的,并且很少更改,因为每个不兼容的更改都会破坏客户端插件。还建议使用具有明确职责的多个模块组织插件代码,例如:

example-plugin-api - 包含API的模块

example-plugin-impl - 包含插件功能代码的模块,这些代码不打算被客户端插件扩展或使用

定义API的一般规则是包括可能由客户端插件代码使用的类。

当然,更复杂的插件可能需要更精细的结构。请参阅Gradle IntelliJ插件-使用示例。

在 Gradle 构建脚本中捆绑 API 源

在最简单的情况下,如果项目由单个模块组成,并且插件 API 在包中明确隔离,例如 com.example.plugin.openapi,包括源 JAR 可以通过将以下片段添加到 Gradle 构建脚本的任务部分来实现:

tasks {
  val createOpenApiSourceJar by registering(Jar::class) {
    // Java sources
    from(sourceSets.main.get().java) {
      include("**/com/example/plugin/openapi/**/*.java")
    }
    // Kotlin sources
    from(kotlin.sourceSets.main.get().kotlin) {
      include("**/com/example/plugin/openapi/**/*.kt")
    }
    destinationDirectory.set(layout.buildDirectory.dir("libs"))
    archiveClassifier.set("src")
  }

  buildPlugin {
    dependsOn(createOpenApiSourceJar)
    from(createOpenApiSourceJar) { into("lib/src") }
  }
}

上述配置将创建一个包含 com.example.plugin.openapi 包中的 Java 和 Kotlin 源文件的源 JAR,并将其添加到所需的 example-plugin.zip!/example-plugin/lib/src 中的最终插件 ZIP 分发中 目录。

如果你的插件是Gradle项目,没有明确的开放API包分离,建议将插件项目重组为Gradle多项目变体,并创建一个专用的开放API子项目,其中包含最终要包含的所有API源 由主要插件 Gradle 项目创建的发行版。

Class Loaders

一个单独的类加载器用于加载每个插件的类。 这允许每个插件使用不同的库版本,即使 IDE 本身或另一个插件使用相同的库。

捆绑库

第三方软件和许可证列出了每个产品的所有捆绑库及其版本。

覆盖 IDE 依赖项

Gradle 7 引入了实现范围,取代了编译范围。

对于此设置,要使用项目定义的依赖项而不是捆绑的 IDE 版本,请将以下代码片段添加到 Gradle 构建脚本中:

configurations.all {
  resolutionStrategy.sortArtifacts(ResolutionStrategy.SortOrder.DEPENDENCY_FIRST)
}

插件依赖类

默认情况下,主 IDE 类加载器加载在插件类加载器中找不到的类。

但是,在 plugin.xml 文件中,您可以使用 <depends> 元素来指定一个插件依赖于一个或多个其他插件。

在这种情况下,这些插件的类加载器将用于当前插件中未找到的类。 这允许插件从其他插件引用类。

使用服务加载器

一些库使用 ServiceLoader 来检测和加载实现。 为了在插件中工作,必须将上下文类加载器设置为插件的类加载器,然后使用初始化代码周围的原始类加载器恢复:

Actions

IntelliJ 平台提供了操作的概念。

动作是从 AnAction 派生的类,当其菜单项或工具栏按钮被选中时,将调用其 actionPerformed() 方法。

操作是用户调用插件功能的最常见方式。

可以使用键盘快捷键或帮助 从菜单或工具栏调用操作。 查找操作…查找。

动作被组织成组,而这些组又可以包含其他组。

一组操作可以形成工具栏或菜单。 组的子组可以形成菜单的子菜单。

用户可以通过菜单和工具栏设置自定义所有已注册的操作。

请参阅动作系统了解如何在 IDE 中创建和注册动作。

Extensions

扩展是插件扩展 IntelliJ 平台功能的最常见方式,其方式不像向菜单或工具栏添加操作那样简单。

以下是使用扩展完成的一些最常见的任务:

com.intellij.toolWindow 扩展点允许插件添加工具窗口(显示在 IDE 用户界面两侧的面板);

com.intellij.applicationConfigurable 和 com.intellij.projectConfigurable 扩展点允许插件将页面添加到设置对话框;

自定义语言插件使用许多扩展点来扩展 IDE 中的各种语言支持功能。

平台和捆绑插件中有 1000 多个可用扩展点,允许自定义 IDE 行为的不同部分。

探索可用的扩展

扩展点和监听器列表列出了 IntelliJ 平台和 IntelliJ IDEA 中捆绑插件的所有可用扩展点。

此外,在第 VIII 部分 — 产品特定下提供了特定于 IDE 的专用扩展点和侦听器列表。 通

过 IntelliJ Platform Explorer 浏览开源 IntelliJ 平台插件的现有实现中的用法。

或者(或在使用第 3 方扩展点时),可以使用 plugin.xml 中 <extensions> 块内的自动完成列出指定命名空间 (defaultExtensionNs) 的所有可用扩展点。

使用视图 查找列表中的快速文档,以访问有关扩展点和实现的更多信息(如果适用)。

有关更多信息和策略,请参阅探索 IntelliJ 平台 API。

Declaring Extensions

为阐明此过程,请考虑 plugin.xml 文件的以下示例部分,该部分定义了两个旨在访问 IntelliJ 平台中的 com.intellij.appStarter 和 com.intellij.projectTemplatesFactory 扩展点的扩展,以及一个用于访问另一个的扩展。

plugin.myExtensionPoint 在另一个插件another.plugin 中的扩展点:

<!--
  Declare extensions to access extension points in the IntelliJ Platform.
  These extension points have been declared using "interface".
 -->
<extensions defaultExtensionNs="com.intellij">
  <appStarter
      implementation="com.example.MyAppStarter"/>
  <projectTemplatesFactory
      implementation="com.example.MyProjectTemplatesFactory"/>
</extensions>

<!--
  Declare extensions to access extension points in a custom plugin "another.plugin".
  The "myExtensionPoint" extension point has been declared using "beanClass"
  and exposes custom properties "key" and "implementationClass".
-->
<extensions defaultExtensionNs="another.plugin">
  <myExtensionPoint
      key="keyValue"
      implementationClass="com.example.MyExtensionPointImpl"/>
</extensions>

Services

服务是当您的插件调用相应 ComponentManager 实例的 getService() 方法时按需加载的插件组件(请参阅类型)。

IntelliJ 平台确保仅加载服务的一个实例,即使它被多次调用也是如此。

服务用于封装在一组相关类上运行的逻辑,或者提供一些可以跨插件项目使用的可重用功能,并且在概念上与其他语言或框架中的服务类没有区别。

服务必须具有用于服务实例化的实现类。 服务也可能有一个接口类,用于获取服务实例并提供服务的 API。

需要关闭挂钩/清理例程的服务可以实现 Disposable 并在 dispose() 中执行必要的工作(请参阅自动处理对象)。

类型

IntelliJ 平台提供三种类型的服务:应用级服务(全局单例)、项目级服务和模块级服务。

对于后两者,为其对应范围的每个实例创建一个单独的服务实例,参见项目模型介绍。

构造函数

项目/模块级服务构造函数可以有一个项目/模块参数。 要提高启动性能,请避免在构造函数中进行任何繁重的初始化。

轻型服务

不会被覆盖的服务不需要在 plugin.xml 中注册(请参阅声明服务)。

相反,使用 @Service 注释服务类。

项目级服务必须指定@Service(Service.Level.PROJECT)。 服务实例将根据调用者在范围内创建(请参阅检索服务)。

限制:

这些属性都不是必需的:os、client、overrides、id、preload。

服务类必须是最终的。

不支持依赖服务的构造函数注入。

如果应用程序级服务是 PersistentStateComponent,则必须禁用漫游 (roamingType = RoamingType.DISABLED)。

Examples

Application-level light service:

@Service
public final class MyAppService {
  public void doSomething(String param) {
    // ...
  }
}

Project-level light service example:

@Service(Service.Level.PROJECT)
public final class MyProjectService {
  private final Project myProject;

  public MyProjectService(Project project) {
    myProject = project;
  }

  public void doSomething(String param) {
    String projectName = myProject.getName();
    // ...
  }
}

声明服务

要注册非轻型服务,为每种类型提供不同的扩展点:

com.intellij.applicationService - 应用级服务

com.intellij.projectService - 项目级服务

com.intellij.moduleService - 模块级服务(不推荐,见上面的注释)

要公开服务 API,请为 serviceInterface 创建单独的类,并在 serviceImplementation 中注册的相应类中扩展它。

如果未指定 serviceInterface,则它应该具有与 serviceImplementation 相同的值。

要为测试/无头环境提供自定义实现,请另外指定 testServiceImplementation/headlessImplementation。

Example

Application-level service:

public interface MyAppService {
  void doSomething(String param);
}

public class MyAppServiceImpl implements MyAppService {
  @Override
  public void doSomething(String param) {
    // ...
  }
}

Project-level service:

public interface MyProjectService {
  void doSomething(String param);
}

public class MyProjectServiceImpl {
  private final Project myProject;

  public MyProjectServiceImpl(Project project) {
    myProject = project;
  }

  public void doSomething(String param) {
    String projectName = myProject.getName();
    // ...
  }
}

Registration in plugin.xml:

<extensions defaultExtensionNs="com.intellij">
  <!-- Declare the application-level service -->
  <applicationService
      serviceInterface="com.example.MyAppService"
      serviceImplementation="com.example.MyAppServiceImpl"/>

  <!-- Declare the project-level service -->
  <projectService
      serviceInterface="com.example.MyProjectService"
      serviceImplementation="com.example.MyProjectServiceImpl"/>
</extensions>

检索服务 Retrieving a Service

获取服务不需要读取操作,可以从任何线程执行。

如果一个服务被多个线程请求,它将在第一个线程中被初始化,其他线程将被阻塞,直到它被完全初始化。

MyAppService applicationService =
    ApplicationManager.getApplication().getService(MyAppService.class);

MyProjectService projectService =
    project.getService(MyProjectService.class);

服务实现可以使用方便的静态 getInstance() 或 getInstance(Project) 方法包装这些调用:

MyAppService applicationService = MyAppService.getInstance();

MyProjectService projectService = MyProjectService.getInstance(project);

示例插件

要阐明如何使用服务,请考虑代码示例中提供的 maxOpenProjects 示例插件

此插件有一个应用程序服务,用于计算 IDE 中当前打开的项目数。 如果此数量超过插件允许的同时打开项目的最大数量 (3),它会显示一条警告消息。

有关如何设置和运行插件的信息,请参阅代码示例。

定义项目级监听器

项目级侦听器以相同的方式注册,除了顶级标记是 <projectListeners>

它们可用于监听项目级事件,例如,工具窗口操作:

<idea-plugin>
  <projectListeners>
    <listener
        class="myPlugin.MyToolWindowListener"
        topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"/>
  </projectListeners>
</idea-plugin>

实现侦听器接口的类可以定义一个接受项目的单参数构造函数,它将接收为其创建侦听器的项目实例:

package myPlugin;

public class MyToolWindowListener implements ToolWindowManagerListener {
  private final Project project;

  public MyToolwindowListener(Project project) {
    this.project = project;
  }

  @Override
  public void stateChanged(@NotNull ToolWindowManager toolWindowManager) {
    // handle the state change
  }
}

附加属性

可以使用以下属性限制侦听器的注册:

os - 允许将侦听器限制为给定的操作系统,例如 os=”windows” 仅适用于 Windows(2020.1 及更高版本)

activeInTestMode - 如果 Application.isUnitTestMode() 返回 true,则设置为 false 以禁用侦听器

activeInHeadlessMode - 如果 Application.isHeadlessEnvironment() 返回 true,则设置为 false 以禁用侦听器。

此外,还涵盖了 activeInTestMode,因为测试模式意味着无头模式。

Extension Points

通过在您的插件中定义扩展点,您可以允许其他插件扩展您的插件的功能。 有两种类型的扩展点:

接口扩展点允许其他插件使用代码扩展您的插件。

当你定义一个接口扩展点时,你指定了一个接口,其他插件将提供实现该接口的类。 然后您将能够调用这些接口上的方法。

Bean 扩展点允许其他插件使用数据扩展您的插件。

您指定扩展类的完全限定名称,其他插件将提供将转换为该类实例的数据。

声明扩展点

您可以在插件配置文件 plugin.xml 的 <extensions><extensionPoints> 部分中声明扩展和扩展点。

要在您的插件中声明扩展点,请将 <extensionPoints> 部分添加到您的 plugin.xml。

然后插入一个子元素 <extensionPoint>,分别在 name、beanClass 和 interface 属性中定义扩展点名称和 bean 类的名称或允许扩展插件功能的接口。

  • myPlugin/META-INF/plugin.xml
<idea-plugin>
  <id>my.plugin</id>

  <extensionPoints>
    <extensionPoint
            name="myExtensionPoint1"
            beanClass="com.example.MyBeanClass"/>

    <extensionPoint
            name="myExtensionPoint2"
            interface="com.example.MyInterface"/>
  </extensionPoints>

</idea-plugin>

name 属性为此扩展点分配一个唯一的名称。 它在使用扩展点中所需的完全限定名称是通过将插件 <id> 前缀为“命名空间”后跟 来构建的。

分隔符:my.plugin.myExtensionPoint1 和 my.plugin.myExtensionPoint2。

beanClass 属性设置一个 bean 类,该类指定一个或多个使用 @Attribute 注释进行注释的属性。

请注意,bean 类不遵循 JavaBean 标准。 实施 PluginAware 以获取有关提供实际扩展的插件的信息。

interface 属性设置了一个接口,该接口对扩展点有贡献的插件必须实现。

area 属性确定扩展将被实例化的范围。 由于扩展应该是无状态的,因此不建议使用非默认。

必须是应用程序的 IDEA_APPLICATION(默认)、项目的 IDEA_PROJECT 或模块范围的 IDEA_MODULE 之一。

对扩展点有贡献的插件将从 plugin.xml 文件中读取这些属性。

sample

为阐明这一点,请考虑上述 plugin.xml 文件中使用的以下示例 MyBeanClass bean 类:

myPlugin/src/com/myplugin/MyBeanClass.java

public class MyBeanClass extends AbstractExtensionPointBean {

  @Attribute("key")
  public String key;

  @Attribute("implementationClass")
  public String implementationClass;

  public String getKey() {
    return key;
  }

  public String getClass() {
    return implementationClass;
  }

}

对于上述扩展点,在 anotherPlugin 中的用法如下所示(另请参见声明扩展):

  • anotherPlugin/META-INF/plugin.xml
<idea-plugin>
  <id>another.plugin</id>

  <!-- Declare dependency on plugin defining extension point: -->
  <depends>my.plugin</depends>

  <!-- Use "my.plugin" namespace: -->
  <extensions defaultExtensionNs="my.plugin">
    <myExtensionPoint1
            key="someKey"
            implementationClass="another.some.implementation.class"/>

    <myExtensionPoint2
            implementation="another.MyInterfaceImpl"/>
  </extension>

</idea-plugin>

使用扩展点

要在运行时引用所有已注册的扩展实例,请声明一个 ExtensionPointName,传入与其在 plugin.xml 中的声明相匹配的完全限定名称。

  • myPlugin/src/com/myplugin/MyExtensionUsingService.java
public class MyExtensionUsingService {

  private static final ExtensionPointName<MyBeanClass> EP_NAME =
          ExtensionPointName.create("my.plugin.myExtensionPoint1");

  public void useExtensions() {
    for (MyBeanClass extension : EP_NAME.getExtensionList()) {
      String key = extension.getKey();
      String clazz = extension.getClass();
      // ...
    }
  }

}

ExtensionPointName 声明的装订线图标允许导航到 plugin.xml 中相应的 <extensionPoint> 声明。

代码洞察可用于扩展点名称字符串文字 (2022.3)。

动态扩展点

要支持动态插件(2020.1 及更高版本),扩展点必须遵守特定的使用规则:

每次使用都会枚举扩展,并且扩展实例不会存储在任何地方

或者,ExtensionPointListener 可以执行必要的数据结构更新(通过 ExtensionPointName.addExtensionPointListener() 注册)

然后可以通过在声明中添加 dynamic=”true” 来将符合这些条件的扩展点标记为动态的:

<extensionPoints>
  <extensionPoint
          name="myDynamicExtensionPoint"
          beanClass="com.example.MyBeanClass"
          dynamic="true"/>
</extensionPoints>

Plugin Configuration File

plugin.xml 配置文件包含有关插件的所有信息,这些信息显示在插件设置对话框中,以及所有已注册的扩展、操作、侦听器等。

下面的部分详细描述了所有元素。

可以在 IntelliJ SDK 文档代码示例存储库中找到示例 plugin.xml 文件。

附加插件配置文件

除了主 plugin.xml 之外,插件还可以包含其他配置文件。

它们具有相同的格式,并且包含在指定插件依赖项的 <depends> 元素的配置文件属性中。

但是,plugin.xml 中所需的某些元素和属性在其他配置文件中会被忽略。

如果要求不同,下面的文档将明确说明。

附加配置文件的一个用例是当插件提供仅在某些 IDE 中可用且需要某些模块的可选功能时。

有用的资源

请确保遵循插件概述页面中的指南,以便在 JetBrains Marketplace 上以最佳方式展示您的插件。

忙碌的插件开发者。

第 2 集更详细地讨论了优化 JetBrains Marketplace 插件页面的 5 个技巧。

另请参阅有关小部件和徽章的市场营销。

Plugin Logo

从版本 2019.1 开始,IntelliJ 平台支持用徽标表示插件。

插件徽标旨在成为插件功能、技术或公司的独特代表。

注意:插件中使用的图标和图像有不同的要求。 有关详细信息,请参阅使用图标和图像。

插件标志用法

插件徽标显示在 JetBrains Marketplace 中。 它们还出现在基于 IntelliJ 平台的 IDE 的设置插件管理器 UI 中。 无论是在线还是在产品 UI 中,插件 Logo 都可以帮助用户在列表中更快地识别插件,如下所示:

plugin

将插件徽标文件添加到插件项目

插件徽标文件必须位于插件分发文件的 META-INF 文件夹中,即您上传到插件存储库并安装到 IDE 中的 plugin.jar 或 plugin.zip 文件。

要在您的分发文件中包含插件徽标文件,请将插件徽标文件放入插件项目的 resources/META-INF 文件夹中。 例如:

icons

Plugin Dependencies

一个插件可能依赖于其他插件的类,这些插件可能是捆绑的、第三方的或同一作者的。

本文档描述了声明插件依赖项和可选插件依赖项的语法。

有关 IntelliJ 平台模块依赖项的更多信息,请参阅本文档的第二部分:与 IntelliJ 平台产品的插件兼容性。

所需步骤

要表达对来自其他插件或模块的类的依赖,请执行本页下方详述的以下三个必需步骤:

找到插件 ID

项目设置

plugin.xml 中的声明

如果在运行时出现了java.lang.NoClassDefFoundError,则意味着第3步被省略或加载插件依赖失败(请查看开发实例的日志文件)。

1. 定位插件 ID 并准备沙盒

必须根据插件的兼容性仔细选择兼容版本。 对于非捆绑插件,无法为依赖插件指定最低/最高版本。 (问题)

JetBrains 市场

对于在 JetBrains Marketplace 上发布的插件:

打开插件的详细信息页面

向下滚动到底部的附加信息部分

复制插件 ID

捆绑和其他插件

使用 Gradle IntelliJ 插件时,可以使用 listBundledPlugins 任务收集所有捆绑的插件。

当使用 DevKit 和非公共插件时,找到包含 META-INF/plugin.xml 描述符的插件主 JAR 文件,带有 <id> 标签(或 <name> 如果未指定)。 捆绑的插件位于 $PRODUCT_ROOT$/plugins/$PLUGIN_NAME$/lib/$PLUGIN_NAME$.jar。

捆绑插件的 ID

下表列出了一些常用的捆绑插件及其 ID。 另请参阅特定于功能的 IntelliJ 社区插件和模块。

2. Project Setup

Depending on the chosen development workflow (Gradle or DevKit), one of the two following steps is necessary.

intellij {
  plugins.set(listOf("com.example.another-plugin:1.0"))
}

3. plugin.xml 中的依赖声明

无论插件项目是使用所有产品中可用的模块,还是使用特定于功能的模块,都必须将正确的模块列为 plugin.xml 中的依赖项。

如果一个项目依赖于另一个插件,则必须像模块一样声明依赖项。 如果仅使用通用 IntelliJ 平台功能 (API),则必须声明对 com.intellij.modules.platform 的默认依赖项。

要显示可用的 IntelliJ 平台模块列表,请在编辑插件项目的 plugin.xml 文件时调用 <depends> 元素内容的代码完成功能。

在 plugin.xml 中,添加一个 <depends> 标签,内容是依赖插件的 ID。

继续上面项目设置中的示例,plugin.xml 中的依赖声明为:

<depends>com.example.another-plugin</depends>

可选的插件依赖项

插件还可以指定可选的插件依赖项。

在这种情况下,即使它依赖的插件没有安装或启用,插件也会加载,但插件的部分功能将不可用。

声明额外的 optional=”true” 和指向可选插件描述符文件的配置文件属性:

<depends
    optional="true"
    config-file="myPluginId-optionalPluginName.xml">dependency.plugin.id</depends>

样品

该插件为 Java 和 Kotlin 文件添加了额外的突出显示。

主 plugin.xml 定义了对 Java 插件(插件 ID com.intellij.java)的必需依赖,并注册了相应的 com.intellij.annotator 扩展。

此外,它还指定了对 Kotlin 插件(插件 ID org.jetbrains.kotlin)的可选依赖项:

  • plugin.xml
<idea-plugin>
   ...
   <depends>com.intellij.java</depends>

   <depends
       optional="true"
       config-file="myPluginId-withKotlin.xml">org.jetbrains.kotlin</depends>

   <extensions defaultExtensionNs="com.intellij">
      <annotator
          language="JAVA"
          implementationClass="com.example.MyJavaAnnotator"/>
   </extensions>
</idea-plugin>

配置文件 myPluginId-withKotlin.xml 与主 plugin.xml 文件位于同一目录中。

在该文件中,定义了 Kotlin 的注释器扩展:

  • myPluginId-withKotlin.xml
<idea-plugin>
   <extensions defaultExtensionNs="com.intellij">
      <annotator
          language="kotlin"
          implementationClass="com.example.MyKotlinAnnotator"/>
   </extensions>
</idea-plugin>

参考资料

https://plugins.jetbrains.com/docs/intellij/plugin-icon-file.html#configuration-structure-overview