feat(plugins): leave room in plugin API 1.0 for what will grow

Released 1.x types never change, so the sets and records that will have
to grow are opened up before the API is frozen:

- Event kinds and action sources are strings; `event`, view `body`,
  `view-event` and `field-value` get an `other(extension)` case (a kind and
  JSON data) that carries later cases to older plugins and IDEs. An IDE
  refuses an event kind it doesn't know, says a view body needs a newer
  IDE, and leaves out unknown form fields.
- Completion items can be snippets, match on `filter-text` and bring
  `additional-edits` (imports). Code actions can carry `data` instead of
  edits; the new `resolve-code-action` export computes them when the user
  picks the action, under the command budget, through the existing resolve
  path.
- Diagnostics name their `source`, carry `tags` (`unnecessary`,
  `deprecated`) and `related` locations.
- The SDK's enums are `#[non_exhaustive]` with `Other` variants;
  `Diagnostic::new`, `Providers::resolve_code_action`.
- README: 1.x calls stay synchronous and pass documents whole; async and
  incremental sync come later behind the host adapter or a new interface.

Official plugins and fixtures rebuilt.
Цей коміт міститься в:
l0ng-ai committed 2026-11-04 20:10:09 +08:00
1 джерело e3c7f362c4
коміт 7c71cf8b6a
56 files changed
+698 -210

No files matched your search

+25 -2
Переглянути файл
@@ -321,6 +321,11 @@ Events are batched: a running plugin gets them within a fraction of a second,
an idle one collects them for 2 s before it is woken, so frequent events
(Key Promoter's actions) don't keep it alive.
Kinds are names (`subscribe` takes a string), so later IDEs add kinds without
a new API: an IDE refuses a kind it doesn't know with `invalid`, and a kind
newer than the plugin's SDK arrives as `Event::Other { kind, data }` (JSON).
The SDK's event enums are `#[non_exhaustive]`; match them with a `_` arm.
### Providers
Declare what the plugin provides in the editor; only plugins whose
@@ -338,7 +343,14 @@ Then `register_plugin!(MyPlugin, providers)` and implement
the document (path, language, version, text). Results join the language
servers' (completion lists, ⌥⏎ quick fixes, ⌘B, gutter icons; a code action
carries edits for any files, applied as one change). A completion item's
`kind` (`CompletionKind` in the SDK, a string in the WIT) picks its icon.
`kind` (`CompletionKind` in the SDK, a string in the WIT) picks its icon; it
can be a `snippet` (`$1`, `${1:name}`, `$0`), match on `filter_text` instead
of its label, and bring `additional_edits` elsewhere in the document (an
import). A code action can leave `edits` empty and set `data`: its edits are
then computed by `resolve_code_action` when the user picks it, under the
command budget instead of the 50 ms. Problems (`publish_diagnostics`) can name
the tool that found them (`source`) and be `unnecessary` (faded) or
`deprecated` (struck through).
Editor providers run under the edit-path budget: 50 ms, after which the
result is dropped, and an interruption at 500 ms. Every plugin providing a
kind is asked at the same time, so each has the whole budget.
@@ -393,6 +405,9 @@ collapse of items, form fields, table cells, inserted and deleted rows, and
`html` messages. A tree is a flat list whose items name their `parent`; an
`expandable` item gets its children when it is expanded. A table gets only
the rows it shows (`viewport`): report `total_rows` and return those.
Bodies, field types and view events added by later APIs travel as `Other {
kind, data }`: an IDE that doesn't know a body says it needs updating, and
leaves out a field type it doesn't know.
Showing a view activates the plugin (`on-view:<id>`). `views` become tool
windows; a `customEditors` file (`priority = "default"`) opens in the
@@ -890,7 +905,10 @@ only grows:
Extend with new types and new functions (or interfaces). `types.error` is
used everywhere and is no exception; new situations use `failed(string)`
or `invalid(string)`. Sets that keep growing (completion, token, symbol and
fold kinds, icons) are strings for this reason.
fold kinds, icons, event kinds, action sources, diagnostic tags) are
strings for this reason, and variants that grow (`event`, view `body`,
`view-event`, `field-value`) have an `other(extension)` case, a kind and
JSON data, that carries later cases to older plugins and IDEs.
2. **Host interfaces (imports) may gain functions and interfaces**: older
components don't import them.
3. **Plugin interfaces (exports) never gain functions** once released: the
@@ -902,6 +920,11 @@ only grows:
minor with a message that says to update the IDE or rebuild with the SDK.
5. **One version number**: the `wit/` package version, `ENGINE_VERSION` and
`API_VERSION` agree, and the SDK's `wit/` copy is identical to `wit/`.
6. **1.x calls are synchronous and pass documents whole.** Host calls block
the plugin's instance, and providers get the document's text with each
call. WASI 0.3 async components come with 2.0, behind an adapter in the
host that keeps 1.x plugins running; incremental document sync, if
needed, is a new interface.
Checked automatically:
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
aa09a92c0ba9d85a01c56bbf4455e40ae44f005b443ce1002bbe8011408ff975
28e4bf10a41bdda011111b0e150ebe96205c57f410bad98c19eea3f1286349d6
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
908c2fd177ad2bb9a8a8492505151c3b81579191156c5ec53f705127f50a8ee9
7e015486cc916f5edbac542863373fd7204f5bcd534f6c408d5f9d2b72913862
+3 -6
Переглянути файл
@@ -97,12 +97,8 @@ fn diagnostic(p: &Problem, text: &str) -> Diagnostic {
let line_len = text.split('\n').nth(p.line as usize).map_or(0, |l| l.encode_utf16().count() as u32);
let start = p.column.unwrap_or(0).min(line_len);
let end = if p.column.is_some() && start < line_len { start + 1 } else { line_len };
Diagnostic {
range: Range::new(Position::new(p.line, start), Position::new(p.line, end.max(start))),
severity: p.severity,
message: p.message.clone(),
code: Some(p.check.clone()),
}
let range = Range::new(Position::new(p.line, start), Position::new(p.line, end.max(start)));
Diagnostic::new(range, p.severity, p.message.clone()).code(p.check.clone())
}
/// The project containing `path`.
@@ -188,6 +184,7 @@ impl Providers for Checkstyle {
kind: Some("quickfix".into()),
preferred: true,
edits: vec![(doc.path.clone(), vec![edit])],
data: None,
});
}
}
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
64270fa0f5b3084c78764f09b8ce161742fdbff9c28fb90590da39594e558db1
a6cf59e74595e13908e2cbf1752c58665c6963de258413c9d7332d2a7dd9cbd0
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
22876f842f727a2b9aa292986df3393f5b5f30c5c34319f5665f7d2701c8accf
b8dde206a74b222b93e61056d4cbe403617ae240d142e01e91420a2aacdb2da4
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
14c60782eca79a25cf2c38d58c05be0eb97c4afc607d4cc83302d1445d094aab
a681c2abd4a65d6aeb62efd514e9b170f4fca52b0c14d2d380a3b04be0a3fbc6
+3 -6
Переглянути файл
@@ -52,12 +52,9 @@ pub fn diagnostics(text: &str, reports: &[Report]) -> Vec<Diagnostic> {
.map(|r| {
let line = r.line.map_or(0, |l| l.saturating_sub(1)).min(lines.len().saturating_sub(1) as u32);
let len = lines.get(line as usize).map_or(0, |l| l.encode_utf16().count() as u32);
Diagnostic {
range: Range::new(Position::new(line, 0), Position::new(line, len)),
severity: if r.error { Severity::Error } else { Severity::Warning },
message: r.message.clone(),
code: Some("graphviz".into()),
}
let range = Range::new(Position::new(line, 0), Position::new(line, len));
let severity = if r.error { Severity::Error } else { Severity::Warning };
Diagnostic::new(range, severity, r.message.clone()).code("graphviz")
})
.collect()
}
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
7f5aacde2b8a8bcee5365c8cbda91045195688899c6537dc2159037cca74a672
3c83bc9559df54457e19e650cdd21bab9d609e22daaa1fc4f596910b70834a34
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
45a7ee993b5c234b0d25e5b05ef60e714fa3edf63d6bd0d6a29b13587c8d09ce
63f1bbdf6eca5a4642cd3453ae384ff3f876de0373c12008b757a63852164b71
+3 -6
Переглянути файл
@@ -129,12 +129,8 @@ pub fn header_edit(path: &str, text: &str, year: i64, owner: &str, license: &str
fn missing(text: &str) -> Diagnostic {
let line = insert_line(text);
let len = text.lines().nth(line as usize).map_or(0, |l| l.encode_utf16().count() as u32);
Diagnostic {
range: Range::new(Position::new(line, 0), Position::new(line, len)),
severity: Severity::Info,
message: "No license header".into(),
code: Some(CODE.into()),
}
Diagnostic::new(Range::new(Position::new(line, 0), Position::new(line, len)), Severity::Info, "No license header")
.code(CODE)
}
/// Reports `path` when it takes a header and has none, clears it otherwise.
@@ -274,6 +270,7 @@ impl Providers for LicenseHeader {
kind: Some("quickfix".into()),
preferred: true,
edits: vec![(doc.path.clone(), vec![edit])],
data: None,
}]
}
}
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
d5cf81455ae8b2b0fd6d1402d8775913bd7999db62b9bdf87675890ce52b1e80
0db5cb24c358651d0e728ab3efb52286290c1a38ec379435225c935d4165ad6a
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
227c8150c6ad7a5a20236764f149c876cbbc3b082763bf2e7fc05de99726b740
257478b050ea360c55e904f2a89c6069293139ff0d97ba860de3f29dac927787
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
bbadcaa7a86a3e0a3f8be6d86aed2229d5cdfd128fda3435b4d64c7e19ad1df2
21e7510d6a3712bf0f21d53a5c8d652e3c2787d89a1fc25998cde96efe124207
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
09cc92e8e731f8f59f3857a71f9a30cc3d9e04a1c32092920f573b0f26592855
04844bfbfbcb1770f4f4742658944c592922b037b2387e9340de58ef0d9baacc