|
| 1 | +# Explanation of the Getting Started |
| 2 | + |
| 3 | +In this section, we'll provide detailed information about the Getting Started. |
| 4 | +If you find it too detailed, feel free to skip this section and move on to the |
| 5 | +next chapter, especially if your goal is to start developing a Denops plugin |
| 6 | +promptly. |
| 7 | + |
| 8 | +## What is Denops? |
| 9 | + |
| 10 | +Denops claims to be an ecosystem for developing Vim / Neovim (hereafter, when we |
| 11 | +refer to Vim without restriction, we also include Neovim) plugins using Deno, |
| 12 | +but, in reality, it is a Vim plugin with the following features: |
| 13 | + |
| 14 | +- Detection and registration of Denops plugins |
| 15 | +- Launching and connecting to Deno processes |
| 16 | +- Calling Deno process-side functions from Vim via RPC (Remote Procedure Call) |
| 17 | +- Calling Vim features from Deno process-side via RPC |
| 18 | + |
| 19 | +By utilizing this plugin, you can control Vim from code written in TypeScript |
| 20 | +(Denops plugins). |
| 21 | + |
| 22 | +> [!NOTE] |
| 23 | +> |
| 24 | +> [RPC (Remote Procedure Call)](https://en.wikipedia.org/wiki/Remote_procedure_call) |
| 25 | +> is used, and while Vim uses a |
| 26 | +> [JSON-based custom specification](https://vim-jp.org/vimdoc-en/channel.html#channel-use), |
| 27 | +> Neovim uses [MessagePack-RPC](https://github.com/msgpack-rpc/msgpack-rpc) (a |
| 28 | +> slightly modified specification). However, Denops abstracts away these |
| 29 | +> differences, so Denops plugin developers don't need to be aware of the RPC |
| 30 | +> specification differences between Vim and Neovim. |
| 31 | +
|
| 32 | +## What is a Vim Plugin? |
| 33 | + |
| 34 | +When Vim starts, it searches for files named `plugin/*.vim` in directories |
| 35 | +specified in `runtimepath`. Additionally, if a function like `foo#bar#hoge()` is |
| 36 | +called, it searches for files named `autoload/foo/bar.vim` in the `runtimepath` |
| 37 | +and reads the file, calling the `foo#bar#hoge()` function defined within. |
| 38 | + |
| 39 | +A Vim plugin is a set of predefined features provided to users, utilizing the |
| 40 | +functionality mentioned above. Typically, an entry point is defined in |
| 41 | +`plugin/{plugin_name}.vim`, and detailed features are implemented in |
| 42 | +`autoload/{plugin_name}.vim` or `autoload/{plugin_name}/*.vim`. For example, |
| 43 | +here is the directory structure for a Vim plugin named `hello`: |
| 44 | + |
| 45 | +``` |
| 46 | +vim-hello |
| 47 | +βββ autoload |
| 48 | +β βββ hello.vim # Defines the function `hello#hello()` |
| 49 | +βββ plugin |
| 50 | + βββ hello.vim # Defines the `Hello` command |
| 51 | +``` |
| 52 | + |
| 53 | +> [!NOTE] |
| 54 | +> |
| 55 | +> For more detailed information on creating Vim plugins, refer to |
| 56 | +> `:help write-plugin`. |
| 57 | +
|
| 58 | +## What is a Denops Plugin? |
| 59 | + |
| 60 | +When Denops is installed, in addition to Vim plugins, files named |
| 61 | +`denops/*/main.ts` are also searched when Vim starts. If a corresponding file is |
| 62 | +found, Denops registers the parent directory name (`foo` in the case of |
| 63 | +`denops/foo/main.ts`) as the plugin name. Then, it imports the corresponding |
| 64 | +file as a TypeScript module and calls the function named `main`. |
| 65 | + |
| 66 | +A Denops plugin, similar to a Vim plugin, provides a set of features written in |
| 67 | +TypeScript to users. Since Denops plugins typically include both TypeScript and |
| 68 | +Vim script code, the directory structure looks like an extension of the Vim |
| 69 | +plugin structure with an added `denops` directory. For example, here is the |
| 70 | +directory structure for a Denops plugin named `hello`: |
| 71 | + |
| 72 | +``` |
| 73 | +denops-hello |
| 74 | +βββ autoload |
| 75 | +β βββ hello.vim # Tasks better written in Vim script (may not exist) |
| 76 | +βββ denops |
| 77 | +β βββ hello |
| 78 | +β βββ main.ts # Entry point for the Denops plugin (mandatory) |
| 79 | +βββ plugin |
| 80 | + βββ hello.vim # Entry point written in Vim script (optional) |
| 81 | +``` |
| 82 | + |
| 83 | +In the Getting Started, we created a file named |
| 84 | +`denops/denops-getting-started/main.ts` and added its parent directory |
| 85 | +(`denops-getting-started`) to `runtimepath`. There were no `autoload` or |
| 86 | +`plugin` directories because we didn't provide an entry point that Vim could |
| 87 | +easily call. |
| 88 | + |
| 89 | +## Understanding the Code in Getting Started |
| 90 | + |
| 91 | +In the Getting Started, we wrote the following code in the `main.ts` file: |
| 92 | + |
| 93 | +```typescript |
| 94 | +import type { Denops } from "https://deno.land/x/[email protected]/mod.ts"; |
| 95 | + |
| 96 | +export function main(denops: Denops): void { |
| 97 | + denops.dispatcher = { |
| 98 | + async hello() { |
| 99 | + await denops.cmd(`echo "Hello, Denops!"`); |
| 100 | + }, |
| 101 | + }; |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +Let's break down this code step by step. |
| 106 | + |
| 107 | +### About Imports |
| 108 | + |
| 109 | +```typescript |
| 110 | +import type { Denops } from "https://deno.land/x/[email protected]/mod.ts"; |
| 111 | +``` |
| 112 | + |
| 113 | +The first line imports the `Denops` type from the [denops_std] standard library. |
| 114 | +You can find detailed information about the library by checking the URL: |
| 115 | +`https://deno.land/x/[email protected]` (remove `/mod.ts`). We fixed the version |
| 116 | +in the import URL, so it's recommended to check for details and update to the |
| 117 | +latest version URL. |
| 118 | + |
| 119 | +Note that we use `import type` syntax, which is part of TypeScript's |
| 120 | +[Type-Only Imports and Export](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). |
| 121 | +This syntax can be written as `import { type Denops }` with the same meaning. |
| 122 | +Using `import { Denops }` for a type-only import is also valid. |
| 123 | + |
| 124 | +> [!NOTE] |
| 125 | +> |
| 126 | +> Denops plugins are dynamically imported, so there might be differences in |
| 127 | +> Denops versions between development and usage. Therefore, to minimize |
| 128 | +> differences between Denops versions, only the `Denops` type information is |
| 129 | +> exposed. The implementation can be found in |
| 130 | +> [`denops/@denops-private/denops.ts`](https://github.com/vim-denops/denops.vim/blob/main/denops/%40denops-private/denops.ts), |
| 131 | +> but it is not publicly exposed for the reasons mentioned above. |
| 132 | +> |
| 133 | +> This type information is provided by [denops_core], and [denops_std] simply |
| 134 | +> re-exports the type information from [denops_core]. However, [denops_core] is |
| 135 | +> intended to be referenced only by [denops.vim] and [denops_std], so Denops |
| 136 | +> plugin developers don't need to use it directly. |
| 137 | +
|
| 138 | +### About Entry Point |
| 139 | + |
| 140 | +```typescript |
| 141 | +export function main(denops: Denops): void { |
| 142 | + // Omitted... |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +The above code exports the `main` function. The `main` function is called by |
| 147 | +Denops, and it takes the |
| 148 | +[Denops instance](https://deno.land/x/denops_std/mod.ts?s=Denops) (`denops`) as |
| 149 | +an argument. Denops plugins use this `denops` to add user-defined APIs or call |
| 150 | +Vim's features. |
| 151 | + |
| 152 | +### About User-Defined APIs |
| 153 | + |
| 154 | +```typescript |
| 155 | +denops.dispatcher = { |
| 156 | + async hello() { |
| 157 | + // Omitted... |
| 158 | + }, |
| 159 | +}; |
| 160 | +``` |
| 161 | + |
| 162 | +The code above adds a user-defined API named `hello` to `denops.dispatcher`. |
| 163 | +`denops.dispatcher` is defined as follows, and each method takes `unknown` types |
| 164 | +for both arguments and return values: |
| 165 | + |
| 166 | +```typescript |
| 167 | +interface Dispatcher { |
| 168 | + [key: string]: (...args: unknown[]) => unknown; |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +By defining methods in `denops.dispatcher`, you can freely define APIs. Since |
| 173 | +the methods registered in `denops.dispatcher` are always called with `await`, |
| 174 | +you can make them asynchronous by returning a `Promise`. |
| 175 | + |
| 176 | +The methods defined in `denops.dispatcher` can be called from Vim using the |
| 177 | +following functions: |
| 178 | + |
| 179 | +| Function | Description | |
| 180 | +| ---------------------- | -------------------------------------------------------------------------------- | |
| 181 | +| `denops#request` | Synchronously calls a user-defined API and returns the result. | |
| 182 | +| `denops#request_async` | Asynchronously calls a user-defined API and passes the result to callbacks. | |
| 183 | +| `denops#notify` | Calls a user-defined API without waiting for completion and discards the result. | |
| 184 | + |
| 185 | +At the end of the Getting Started, we used |
| 186 | +`denops#request('denops-getting-started', 'hello', [])` to call the user-defined |
| 187 | +API named `hello` in `denops-getting-started` plugin. |
| 188 | + |
| 189 | +### About Calling Vim's features |
| 190 | + |
| 191 | +```typescript |
| 192 | +await denops.cmd(`echo "Hello, Denops!"`); |
| 193 | +``` |
| 194 | + |
| 195 | +With the received `denops`, you can call Vim functions, execute Vim commands, or |
| 196 | +evaluate Vim expressions. In the example above, the `hello` API internally uses |
| 197 | +`denops.cmd` to execute the `echo` command in Vim. The `denops` object provides |
| 198 | +several methods: |
| 199 | + |
| 200 | +| Method | Description | |
| 201 | +| ---------- | ---------------------------------------------------------------------------------------------------------- | |
| 202 | +| `call` | Calls a Vim function and returns the result. | |
| 203 | +| `batch` | Calls multiple Vim functions in bulk and returns the results in bulk. | |
| 204 | +| `cmd` | Executes a Vim command. If `ctx` is provided, it is expanded as local variables. | |
| 205 | +| `eval` | Evaluate a Vim expression and returns the result. If `ctx` is provided, it is expanded as local variables. | |
| 206 | +| `dispatch` | Calls a user-defined API of another Denops plugin and returns the result. | |
| 207 | + |
| 208 | +Although `denops` provides low-level interfaces, [denops_std] combines these |
| 209 | +low-level interfaces to offer higher-level interfaces. Therefore, it's |
| 210 | +recommended to use [denops_std] to call Vim's features in actual plugin |
| 211 | +development. |
| 212 | + |
| 213 | +For example, use |
| 214 | +[`function` module ](https://deno.land/x/[email protected]/function/mod.ts) to |
| 215 | +call Vim's function instead of `denops.call` like: |
| 216 | + |
| 217 | +```typescript |
| 218 | +import * as fn from "https://deno.land/x/[email protected]/function/mod.ts"; |
| 219 | + |
| 220 | +// Bad (result1 is `unknown`) |
| 221 | +const result1 = await denops.call("expand", "%"); |
| 222 | + |
| 223 | +// Good (result2 is `string`) |
| 224 | +const result2 = await fn.expand(denops, "%"); |
| 225 | +``` |
| 226 | + |
| 227 | +If developers use `function` module instead, they can benefit from features like |
| 228 | +auto-completion and type checking provided by LSP (Language Server Protocol). |
| 229 | + |
| 230 | +## Next Steps |
| 231 | + |
| 232 | +In the next step, follow the tutorial to learn how to develop a minimum Denops |
| 233 | +plugin. |
| 234 | + |
| 235 | +- [Tutorial (Hello World)](../tutorial/helloworld/README.md) |
| 236 | +- [Tutorial (Maze)](../tutorial/maze/README.md) |
| 237 | +- [API reference](https://deno.land/x/denops_std/mod.ts) |
| 238 | + |
| 239 | +[denops.vim]: https://github.com/vim-denops/denops.vim |
| 240 | +[denops_std]: https://deno.land/x/denops_std |
| 241 | +[denops_core]: https://deno.land/x/denops_core |
0 commit comments