mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
API 1.1.0 shipped with 0.1.37 and its WIT is frozen in the released snapshot, so additions now go into 1.2.0. The package version in wit/ and the SDK's copy, ENGINE_VERSION and both API_VERSION constants move to 1.2; the official plugins and test fixtures are rebuilt against it. No interface, type or function changes: 1.0 and 1.1 components keep linking. The release that ships 1.2 copies wit/ over sdk/rust/openidea-plugin/tests/api/released/.
193 lines
6.9 KiB
Rust
193 lines
6.9 KiB
Rust
//! Test fixture for the ide-plugin sandbox. Never shipped.
|
|
|
|
// `plugin` with syntax and editor providers.
|
|
wit_bindgen::generate!({
|
|
inline: "
|
|
package fixture:misbehave;
|
|
world misbehave {
|
|
include openidea:plugin/[email protected];
|
|
export openidea:plugin/[email protected];
|
|
}
|
|
",
|
|
path: "../../../../../wit",
|
|
world: "fixture:misbehave/misbehave",
|
|
generate_all,
|
|
});
|
|
|
|
use exports::openidea::plugin::providers::{
|
|
self, CodeAction, CompletionItem, Document, FileEdit, InlineSuggestion, LineMarker,
|
|
};
|
|
use exports::openidea::plugin::syntax_provider::{self, Fold, Symbol, Token};
|
|
use exports::openidea::plugin::{commands, event_handler, lifecycle};
|
|
use openidea::plugin::{invocation::Invocation, types};
|
|
|
|
struct Misbehave;
|
|
|
|
fn failed(message: &str) -> types::Error {
|
|
types::Error::Failed(message.into())
|
|
}
|
|
|
|
impl lifecycle::Guest for Misbehave {
|
|
fn activate(ctx: types::Context) -> Result<(), types::Error> {
|
|
// A deliberately broken build (rollback tests): `version = "…-broken"`.
|
|
if ctx.plugin_version.ends_with("-broken") {
|
|
return Err(failed("this build is broken"));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn deactivate() {}
|
|
}
|
|
|
|
impl commands::Guest for Misbehave {
|
|
fn run(id: String, ctx: &Invocation, arg: Option<String>) -> Result<Option<String>, types::Error> {
|
|
match id.as_str() {
|
|
"echo" => Ok(arg),
|
|
"panic" => panic!("boom"),
|
|
"spin" => {
|
|
let mut x: u64 = 0;
|
|
loop {
|
|
x = std::hint::black_box(x.wrapping_add(1));
|
|
}
|
|
}
|
|
"alloc" => {
|
|
let mut chunks: Vec<Vec<u8>> = Vec::new();
|
|
loop {
|
|
chunks.push(vec![1u8; 16 << 20]);
|
|
std::hint::black_box(&chunks);
|
|
}
|
|
}
|
|
"read" => {
|
|
let project = ctx.project().ok_or_else(|| failed("no project is open"))?;
|
|
project.read_file(&arg.unwrap_or_default()).map(Some)
|
|
}
|
|
"root" => ctx.project().map(|p| p.root()).transpose(),
|
|
// `editor.write` is not granted: an error, not a trap.
|
|
"insert" => {
|
|
let editor = ctx.editor().ok_or_else(|| failed("no editor"))?;
|
|
editor.apply_edits(0, &[]).map(|_| None)
|
|
}
|
|
// The user gave up: not the plugin failing.
|
|
"cancel" => Err(types::Error::Cancelled),
|
|
// Event kinds are names: one this IDE doesn't know is refused.
|
|
"subscribe" => openidea::plugin::events::subscribe(&arg.unwrap_or_default()).map(|()| None),
|
|
// Tagged problems from another tool on the line 0 of `arg`.
|
|
"diagnose" => {
|
|
use openidea::plugin::diagnostics::{self, Diagnostic, Severity};
|
|
let start = types::Position { line: 0, character: 0 };
|
|
let d = Diagnostic {
|
|
range: types::Range { start, end: start },
|
|
severity: Severity::Warning,
|
|
message: "unused".into(),
|
|
code: None,
|
|
source: Some("lint".into()),
|
|
tags: vec!["unnecessary".into(), "glowing".into()],
|
|
related: Vec::new(),
|
|
};
|
|
diagnostics::publish(&arg.unwrap_or_default(), &[d]).map(|()| None)
|
|
}
|
|
other => Err(types::Error::NotFound(format!("command {other}"))),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl event_handler::Guest for Misbehave {
|
|
fn handle(_events: Vec<openidea::plugin::events::Event>, _ctx: &Invocation) {}
|
|
}
|
|
|
|
/// Edit-path budgets: the text says how to behave. `sleep:<ms>` busy-waits
|
|
/// that long, `spin` never returns, `panic` traps.
|
|
fn behave(text: &str) {
|
|
if let Some(ms) = text.strip_prefix("sleep:").and_then(|n| n.trim().parse::<u64>().ok()) {
|
|
let until = std::time::Instant::now() + std::time::Duration::from_millis(ms);
|
|
while std::time::Instant::now() < until {
|
|
std::hint::black_box(0);
|
|
}
|
|
} else if text == "spin" {
|
|
loop {
|
|
std::hint::black_box(0);
|
|
}
|
|
} else if text == "panic" {
|
|
panic!("boom");
|
|
}
|
|
}
|
|
|
|
/// One keyword token over the whole text, after [`behave`].
|
|
impl syntax_provider::Guest for Misbehave {
|
|
fn highlights(_language: String, text: String) -> Vec<Token> {
|
|
behave(&text);
|
|
vec![Token { start: 0, end: text.len() as u32, kind: "keyword".into() }]
|
|
}
|
|
|
|
fn outline(_language: String, _text: String) -> Vec<Symbol> {
|
|
Vec::new()
|
|
}
|
|
|
|
fn folding(_language: String, _text: String) -> Vec<Fold> {
|
|
Vec::new()
|
|
}
|
|
}
|
|
|
|
/// Completion: one item, labelled with the document's path, after [`behave`].
|
|
impl providers::Guest for Misbehave {
|
|
fn completion(doc: Document, _at: types::Position, _trigger: Option<String>) -> Vec<CompletionItem> {
|
|
behave(&doc.text);
|
|
// `rich`: a snippet that also adds an import.
|
|
let rich = doc.text == "rich";
|
|
let start = types::Position { line: 0, character: 0 };
|
|
vec![CompletionItem {
|
|
label: doc.path.clone(),
|
|
detail: None,
|
|
kind: Some("keyword".into()),
|
|
insert_text: if rich { "f(${1:x})$0".into() } else { doc.path },
|
|
snippet: rich,
|
|
replace: None,
|
|
sort_text: None,
|
|
filter_text: rich.then(|| "fun".into()),
|
|
documentation: None,
|
|
additional_edits: if rich {
|
|
vec![types::TextEdit { range: types::Range { start, end: start }, text: "use f;\n".into() }]
|
|
} else {
|
|
Vec::new()
|
|
},
|
|
}]
|
|
}
|
|
|
|
fn inline_completion(_doc: Document, _at: types::Position) -> Option<InlineSuggestion> {
|
|
None
|
|
}
|
|
|
|
/// `lazy`: one action whose edits come from `resolve-code-action`.
|
|
fn code_actions(doc: Document, _selection: types::Range) -> Vec<CodeAction> {
|
|
if doc.text != "lazy" {
|
|
return Vec::new();
|
|
}
|
|
vec![CodeAction {
|
|
title: "Fix later".into(),
|
|
kind: Some("quickfix".into()),
|
|
preferred: false,
|
|
edits: Vec::new(),
|
|
data: Some("fix".into()),
|
|
}]
|
|
}
|
|
|
|
fn resolve_code_action(doc: Document, action: CodeAction) -> Result<CodeAction, types::Error> {
|
|
if action.data.as_deref() != Some("fix") {
|
|
return Err(types::Error::Invalid("not one of mine".into()));
|
|
}
|
|
let start = types::Position { line: 0, character: 0 };
|
|
let edit = types::TextEdit { range: types::Range { start, end: start }, text: "fixed ".into() };
|
|
Ok(CodeAction { edits: vec![FileEdit { path: doc.path, edits: vec![edit] }], ..action })
|
|
}
|
|
|
|
fn definition(_doc: Document, _at: types::Position) -> Vec<types::Location> {
|
|
Vec::new()
|
|
}
|
|
|
|
fn line_markers(_doc: Document) -> Vec<LineMarker> {
|
|
Vec::new()
|
|
}
|
|
}
|
|
|
|
export!(Misbehave);
|