diff --git a/crates/ide-host/tests/plugins.rs b/crates/ide-host/tests/plugins.rs index c51620c7..2fdf31fe 100644 --- a/crates/ide-host/tests/plugins.rs +++ b/crates/ide-host/tests/plugins.rs @@ -2339,9 +2339,14 @@ fn commands_ask_the_user_and_reveal_their_views() { let (result, prompt) = run_and_answer(&f, "input", Some("Main"), Some(&["Other"]), none); assert_eq!(result.unwrap().as_deref(), Some("Other")); - assert_eq!(prompt, Prompt::Input { prompt: "Name".into(), value: Some("Main".into()) }); + assert_eq!(prompt, Prompt::Input { prompt: "Name".into(), value: Some("Main".into()), multiline: false }); assert_eq!(run_and_answer(&f, "input", None, None, none).0.unwrap().as_deref(), Some("none")); + let (result, prompt) = run_and_answer(&f, "input-multiline", Some("one\ntwo"), Some(&["a\nb"]), none); + assert_eq!(result.unwrap().as_deref(), Some("a\nb")); + assert_eq!(prompt, Prompt::Input { prompt: "Name".into(), value: Some("one\ntwo".into()), multiline: true }); + assert_eq!(run_and_answer(&f, "input-multiline", None, None, none).0.unwrap().as_deref(), Some("none")); + let (result, prompt) = run_and_answer(&f, "confirm", None, Some(&[]), none); assert_eq!(result.unwrap().as_deref(), Some("true")); assert_eq!(prompt, Prompt::Confirm { title: "Delete".into(), message: "Really?".into() }); @@ -2360,7 +2365,7 @@ fn commands_ask_the_user_and_reveal_their_views() { // Without a window there is nothing to attach the dialog to. f.take_events(); let no_window = Invocation { client: None, ..f.inv() }; - for cmd in ["pick", "input", "confirm"] { + for cmd in ["pick", "input", "input-multiline", "confirm"] { let err = f.host.run_command("jobs", cmd, None, no_window.clone()).unwrap_err().message; assert!(err.contains("from a command the user invoked"), "{cmd}: {err}"); } diff --git a/crates/ide-plugin/src/types.rs b/crates/ide-plugin/src/types.rs index 1156e077..a3106f9c 100644 --- a/crates/ide-plugin/src/types.rs +++ b/crates/ide-plugin/src/types.rs @@ -77,14 +77,15 @@ pub struct PickItem { pub description: Option, } -/// What a plugin asks the user (`ui.pick`, `ui.pick-many`, `ui.input`, `ui.confirm`). +/// What a plugin asks the user (`ui.pick`, `ui.pick-many`, `ui.input`, +/// `ui.input-multiline`, `ui.confirm`). #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Type)] #[serde(tag = "kind", rename_all = "camelCase")] pub enum Prompt { #[serde(rename_all = "camelCase")] QuickPick { title: String, items: Vec, multiple: bool }, #[serde(rename_all = "camelCase")] - Input { prompt: String, value: Option }, + Input { prompt: String, value: Option, multiline: bool }, #[serde(rename_all = "camelCase")] Confirm { title: String, message: String }, } diff --git a/crates/ide-plugin/src/wit_host.rs b/crates/ide-plugin/src/wit_host.rs index fbfacbd3..44eaef7a 100644 --- a/crates/ide-plugin/src/wit_host.rs +++ b/crates/ide-plugin/src/wit_host.rs @@ -355,7 +355,14 @@ impl ui::Host for StoreData { } fn input(&mut self, prompt: String, value: Option) -> Fallible> { - let prompt = Prompt::Input { prompt: clip(prompt, MAX_TITLE), value: value.map(|v| clip(v, 4096)) }; + let prompt = + Prompt::Input { prompt: clip(prompt, MAX_TITLE), value: value.map(|v| clip(v, 4096)), multiline: false }; + Ok(self.ask(prompt).map(|answer| answer.and_then(|text| text.into_iter().next()))) + } + + fn input_multiline(&mut self, prompt: String, value: Option) -> Fallible> { + let prompt = + Prompt::Input { prompt: clip(prompt, MAX_TITLE), value: value.map(|v| clip(v, 4096)), multiline: true }; Ok(self.ask(prompt).map(|answer| answer.and_then(|text| text.into_iter().next()))) } diff --git a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm index 8f307470..e20cb7ab 100644 Binary files a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources index 09fad6f9..8cc6e980 100644 --- a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources @@ -1 +1 @@ -8a4006a730cf82fa412e28c1d181cdb96b7d16e5fe9a635b735d96d06862a80b +19b672d86ff84edbbdb32ba06aefba08344e28b00310c09260d935a6fd2d6d92 diff --git a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm index ba88bd79..8be66269 100644 Binary files a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources index dbe23ced..60afeff3 100644 --- a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources @@ -1 +1 @@ -f576b03db2e7bfaad3d5fc8c562c678c245d096bfa0c52bcadba4663991012bc +5916e03cba741eea838c99757a50b86ecae020a75dc856d0301a5afcd4beb437 diff --git a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm index efcad8ab..6d75c5c9 100644 Binary files a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources index 31b75c0a..a7b4f195 100644 --- a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources @@ -1 +1 @@ -52ae124ebab17dbbb182997efe13bdcd48943f3fcb7730f1cdf466cdbd1fc40b +cb9b6f980b976d04ff25289a7978fe6f0b1f77e99ac9bdbaa8fd0720e8306f29 diff --git a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm index ed7a7ecb..498d2e37 100644 Binary files a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources index f71786d4..4cfbfba6 100644 --- a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources @@ -1 +1 @@ -5bfac74cff6560738bbd639691211219cfe731e260cf02bb86aec3bd8f4edc06 +e399272912a4664c9ce307ff128ce2059a2276e31b7806e1553870c4041c27b5 diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm index 6be609a4..b3939b9e 100644 Binary files a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources index 415510c6..c6f21487 100644 --- a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources @@ -1 +1 @@ -4a9eadbf94cc03648fd51e2ed892d23bdae68084762098981dce2a15b97c3fbd +5ad3afb9518e1e2d06e05b5ba5d7339b8aafa7d03502cad00bdacbfe73419775 diff --git a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm index fa2d0592..4517c690 100644 Binary files a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources index 8abe3e13..39738637 100644 --- a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources @@ -1 +1 @@ -2c3be2b558b10824cd2420df5de0b7dee30c68a63cffc3f03cfae16ee2d96574 +93840a797e8f6d86e3d75432395370f19e6a632444d8441b346b9db6c32cd6f9 diff --git a/crates/ide-plugin/tests/fixtures/jobs/src/lib.rs b/crates/ide-plugin/tests/fixtures/jobs/src/lib.rs index 939ba0ae..9ec2d17b 100644 --- a/crates/ide-plugin/tests/fixtures/jobs/src/lib.rs +++ b/crates/ide-plugin/tests/fixtures/jobs/src/lib.rs @@ -75,6 +75,7 @@ impl Plugin for Jobs { Ok(Some(picked.map_or("none".to_owned(), |keys| keys.join(",")))) } "input" => Ok(Some(host::input("Name", arg)?.unwrap_or_else(|| "none".to_owned()))), + "input-multiline" => Ok(Some(host::input_multiline("Name", arg)?.unwrap_or_else(|| "none".to_owned()))), "confirm" => Ok(Some(host::confirm("Delete", "Really?")?.to_string())), "reveal" => host::reveal_view(arg.unwrap_or_default()).map(|()| None), "selection" => { diff --git a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm index d6f2fc30..1da6e94d 100644 Binary files a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources index a5d3ad78..05d89826 100644 --- a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources @@ -1 +1 @@ -e1cbbe2c814e554ef9622204ddcfce87c4d1919993d5e78f9739dfaa46ba128c +44a29b9c311e10a055625fc43940fb9ecd361639f8a11c992ac6817f39e95e36 diff --git a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm index e85457d1..be1795bb 100644 Binary files a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources index fad98daf..4588151c 100644 --- a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources @@ -1 +1 @@ -332840c4966244f309be9853c44caae56a5706bc102b9df0b4de4475a2a5d2fd +36a33de65d506d7753722d38aea7576722fba583ef615de502a5e341b781ef18 diff --git a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm index 36c158c7..04ee398f 100644 Binary files a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources index 22a0f575..00da4599 100644 --- a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources @@ -1 +1 @@ -ac8c4a1b4b4df9c3ffd4725e321df3f7836955a1c052387a276f0ad075c85527 +a4f9b7a13a7c2aa1243359df865e659eb4b8f86fb6623d743dcc17988a10e183 diff --git a/plugins/README.md b/plugins/README.md index 9c3e646b..fb7dcf29 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -381,6 +381,7 @@ then run `openidea-plugin check`. | `host::open_url(url)` | `ui.open-url` | Opens an `http://` or `https://` URL in the user's default browser. | | `host::pick(title, items)`, `pick_many` | `ui.pick`, `ui.pick-many` | Asks the user to pick from `PickItem`s and waits: the chosen key(s), or `None` when dismissed. Only from a command or event of a window (else `invalid`); the wait isn't charged to the time limit. | | `host::input(prompt, value)` | `ui.input` | A one-line text box pre-filled with `value`; `None` when dismissed. Same rules as `pick`. | +| `host::input_multiline(prompt, value)` | `ui.input-multiline` | A multi-line text box pre-filled with `value`; `None` when dismissed; Cmd/Ctrl+Enter confirms. Same rules as `input`. | | `host::confirm(title, body)` | `ui.confirm` | OK / Cancel; `true` on OK. Same rules as `pick`. | | `host::locale()` | `ui.locale` | The IDE's UI language as a BCP 47 tag (`en`, `fr`, `zh-CN`); no capability. See _Translations_. | | `host::log(level, msg)` | `log` | IDE log, tagged with the plugin id. `eprintln!` output is logged too and shown when the plugin crashes. | diff --git a/plugins/official/ai-completion/dist/plugin.wasm b/plugins/official/ai-completion/dist/plugin.wasm index 191a729c..5016aa48 100644 Binary files a/plugins/official/ai-completion/dist/plugin.wasm and b/plugins/official/ai-completion/dist/plugin.wasm differ diff --git a/plugins/official/ai-completion/dist/plugin.wasm.sources b/plugins/official/ai-completion/dist/plugin.wasm.sources index 91db1e46..fd30e5f1 100644 --- a/plugins/official/ai-completion/dist/plugin.wasm.sources +++ b/plugins/official/ai-completion/dist/plugin.wasm.sources @@ -1 +1 @@ -e6d7996c21d41d1cfe585c1037fbe4b04ba523c5fdba676f0852ee8a8438f6c9 +103165fc45ab6a995cdddd10848ae81f3f137438c1981375e6741185813ccdb3 diff --git a/plugins/official/checkstyle/dist/plugin.wasm b/plugins/official/checkstyle/dist/plugin.wasm index 5d0d07ff..5a638086 100644 Binary files a/plugins/official/checkstyle/dist/plugin.wasm and b/plugins/official/checkstyle/dist/plugin.wasm differ diff --git a/plugins/official/checkstyle/dist/plugin.wasm.sources b/plugins/official/checkstyle/dist/plugin.wasm.sources index dd3710b5..723aa76d 100644 --- a/plugins/official/checkstyle/dist/plugin.wasm.sources +++ b/plugins/official/checkstyle/dist/plugin.wasm.sources @@ -1 +1 @@ -2cc22faa9f1a4b4c27d22f740144e5968083444898c36574d1b3e7780e003dc0 +e0845829cfaa2d8f471ccfd8a41726757443df6591e1e06be9e499d6781c02fe diff --git a/plugins/official/csv-editor/dist/plugin.wasm b/plugins/official/csv-editor/dist/plugin.wasm index 4f93512a..9742d6f3 100644 Binary files a/plugins/official/csv-editor/dist/plugin.wasm and b/plugins/official/csv-editor/dist/plugin.wasm differ diff --git a/plugins/official/csv-editor/dist/plugin.wasm.sources b/plugins/official/csv-editor/dist/plugin.wasm.sources index ab28ff18..068f8ec7 100644 --- a/plugins/official/csv-editor/dist/plugin.wasm.sources +++ b/plugins/official/csv-editor/dist/plugin.wasm.sources @@ -1 +1 @@ -2ed5f73c78e165a494d5f91f6ed2b55682e28a9d4e516553c9332b9d8ab84ab4 +6490dcca17d4d87336c70bbf8fbfac90f711ed0079f43d16534703b179211069 diff --git a/plugins/official/git-toolbox/dist/plugin.wasm b/plugins/official/git-toolbox/dist/plugin.wasm index b238f5dd..f10f1576 100644 Binary files a/plugins/official/git-toolbox/dist/plugin.wasm and b/plugins/official/git-toolbox/dist/plugin.wasm differ diff --git a/plugins/official/git-toolbox/dist/plugin.wasm.sources b/plugins/official/git-toolbox/dist/plugin.wasm.sources index fa91f9cb..2cfcdf3b 100644 --- a/plugins/official/git-toolbox/dist/plugin.wasm.sources +++ b/plugins/official/git-toolbox/dist/plugin.wasm.sources @@ -1 +1 @@ -3274e8023091ec67b018d10e67e6180c3bc875747d51779a3e67ebfe76178907 +aa807e1f712896e0ffe1f16115bd8ab991b59df5fcae73e97b6a40d6c94aaf8f diff --git a/plugins/official/graphviz/dist/plugin.wasm b/plugins/official/graphviz/dist/plugin.wasm index 9b5b24cb..81f6a368 100644 Binary files a/plugins/official/graphviz/dist/plugin.wasm and b/plugins/official/graphviz/dist/plugin.wasm differ diff --git a/plugins/official/graphviz/dist/plugin.wasm.sources b/plugins/official/graphviz/dist/plugin.wasm.sources index 7f64b5c9..f678649c 100644 --- a/plugins/official/graphviz/dist/plugin.wasm.sources +++ b/plugins/official/graphviz/dist/plugin.wasm.sources @@ -1 +1 @@ -6060665c10f800787c9c7d03fbbd7dcdefb9fe475827b6b6902fdd72324f7d98 +225ca906d21299fa45059c11d12468e6c3fc577aebb59441c51774a760e54a26 diff --git a/plugins/official/key-promoter/dist/plugin.wasm b/plugins/official/key-promoter/dist/plugin.wasm index 16096aaf..8c61f485 100644 Binary files a/plugins/official/key-promoter/dist/plugin.wasm and b/plugins/official/key-promoter/dist/plugin.wasm differ diff --git a/plugins/official/key-promoter/dist/plugin.wasm.sources b/plugins/official/key-promoter/dist/plugin.wasm.sources index fad50328..0c08f9c6 100644 --- a/plugins/official/key-promoter/dist/plugin.wasm.sources +++ b/plugins/official/key-promoter/dist/plugin.wasm.sources @@ -1 +1 @@ -929cbc24e4c07131e8a46aa333632b703909b6d978b36b474207f6ca233cc9c1 +189e378fc52adfb058243feef4e100835600a1d4751adc23a262d13e703ef5e9 diff --git a/plugins/official/license-header/dist/plugin.wasm b/plugins/official/license-header/dist/plugin.wasm index d4729e4a..262c449b 100644 Binary files a/plugins/official/license-header/dist/plugin.wasm and b/plugins/official/license-header/dist/plugin.wasm differ diff --git a/plugins/official/license-header/dist/plugin.wasm.sources b/plugins/official/license-header/dist/plugin.wasm.sources index f6a069ce..37e0ba50 100644 --- a/plugins/official/license-header/dist/plugin.wasm.sources +++ b/plugins/official/license-header/dist/plugin.wasm.sources @@ -1 +1 @@ -2f26af0ac018d56a0f93a7060ac0a4180b9760c20a98465ef9d4a6f06878455b +2b2e162de966b670918b1bbe937c34cf9bef014834fb38be820ec25f20b09159 diff --git a/plugins/official/mybatis/dist/plugin.wasm b/plugins/official/mybatis/dist/plugin.wasm index 736d37b3..e845ea52 100644 Binary files a/plugins/official/mybatis/dist/plugin.wasm and b/plugins/official/mybatis/dist/plugin.wasm differ diff --git a/plugins/official/mybatis/dist/plugin.wasm.sources b/plugins/official/mybatis/dist/plugin.wasm.sources index 0d1805bb..0b3c74d1 100644 --- a/plugins/official/mybatis/dist/plugin.wasm.sources +++ b/plugins/official/mybatis/dist/plugin.wasm.sources @@ -1 +1 @@ -7aafd74237661ec2907a4c8950b685a17bed760a10a2fc1062139171636e7f2d +6d13cd671dfed3c9c73be0e40b81cc5f757e4f8ebf6a9eecba0001fa8c314d47 diff --git a/plugins/official/regex-tester/dist/plugin.wasm b/plugins/official/regex-tester/dist/plugin.wasm index 6c56e4a0..c35c9f14 100644 Binary files a/plugins/official/regex-tester/dist/plugin.wasm and b/plugins/official/regex-tester/dist/plugin.wasm differ diff --git a/plugins/official/regex-tester/dist/plugin.wasm.sources b/plugins/official/regex-tester/dist/plugin.wasm.sources index b21dadee..cfd5d464 100644 --- a/plugins/official/regex-tester/dist/plugin.wasm.sources +++ b/plugins/official/regex-tester/dist/plugin.wasm.sources @@ -1 +1 @@ -af6ba8a45e97858fbc9c530bdb8e9e56c6229c9715a91bd543399fa0bc7cc04f +a941b31f326427d41cf3e62373bc421d086789a0ac1cb8c3647eafafe29c5834 diff --git a/plugins/official/solidity/dist/plugin.wasm b/plugins/official/solidity/dist/plugin.wasm index de7498e0..e65107f6 100644 Binary files a/plugins/official/solidity/dist/plugin.wasm and b/plugins/official/solidity/dist/plugin.wasm differ diff --git a/plugins/official/solidity/dist/plugin.wasm.sources b/plugins/official/solidity/dist/plugin.wasm.sources index 74ceed13..cc89ff3d 100644 --- a/plugins/official/solidity/dist/plugin.wasm.sources +++ b/plugins/official/solidity/dist/plugin.wasm.sources @@ -1 +1 @@ -e9f8ce2cf2d1cc4bcd05f4fed106d11542a2fa4b12e256fe675cbb0aa41dd3af +86428f64ea11d4863794595f12a9c100423725981d66d6f2a862191a0951fecf diff --git a/plugins/official/string-tools/dist/plugin.wasm b/plugins/official/string-tools/dist/plugin.wasm index e853e15d..16a22e67 100644 Binary files a/plugins/official/string-tools/dist/plugin.wasm and b/plugins/official/string-tools/dist/plugin.wasm differ diff --git a/plugins/official/string-tools/dist/plugin.wasm.sources b/plugins/official/string-tools/dist/plugin.wasm.sources index 8cd3dd33..bfa8f5f9 100644 --- a/plugins/official/string-tools/dist/plugin.wasm.sources +++ b/plugins/official/string-tools/dist/plugin.wasm.sources @@ -1 +1 @@ -737d1a1eb063a6757e93f0359fc32ab2b624ef8c251a4cba31aa3c36f7587002 +fc3b11c7b09f9a57c319af579efdd6f00d9ed51dccc6e86404c5164c6460d007 diff --git a/sdk/rust/openidea-plugin/src/host.rs b/sdk/rust/openidea-plugin/src/host.rs index 7f95befd..393858ff 100644 --- a/sdk/rust/openidea-plugin/src/host.rs +++ b/sdk/rust/openidea-plugin/src/host.rs @@ -117,6 +117,10 @@ mod imp { wit::ui::input(prompt, value).map_err(error) } + pub fn input_multiline(prompt: &str, value: Option<&str>) -> Result, Error> { + wit::ui::input_multiline(prompt, value).map_err(error) + } + pub fn confirm(title: &str, body: &str) -> Result { wit::ui::confirm(title, body).map_err(error) } @@ -730,6 +734,12 @@ pub fn input(prompt: &str, value: Option<&str>) -> Result, Error> imp::input(prompt, value) } +/// Asks for text over several lines, pre-filled with `value`; `None` when +/// dismissed. Same rules as [`input`]; the dialog confirms on Cmd/Ctrl+Enter. +pub fn input_multiline(prompt: &str, value: Option<&str>) -> Result, Error> { + imp::input_multiline(prompt, value) +} + /// Asks for confirmation: `true` on OK, `false` on Cancel or Escape. /// Same rules as [`pick`]. pub fn confirm(title: &str, body: &str) -> Result { diff --git a/sdk/rust/openidea-plugin/src/mock.rs b/sdk/rust/openidea-plugin/src/mock.rs index 472a010b..61877978 100644 --- a/sdk/rust/openidea-plugin/src/mock.rs +++ b/sdk/rust/openidea-plugin/src/mock.rs @@ -491,6 +491,10 @@ pub mod current { Ok(with(|s| s.answers.pop_front().flatten()).and_then(|a| a.into_iter().next())) } + pub fn input_multiline(_prompt: &str, _value: Option<&str>) -> Result, Error> { + Ok(with(|s| s.answers.pop_front().flatten()).and_then(|a| a.into_iter().next())) + } + pub fn confirm(_title: &str, _body: &str) -> Result { Ok(with(|s| s.answers.pop_front().flatten()).is_some()) } @@ -937,6 +941,10 @@ mod tests { assert!(matches!(host::pick_many("Pick", &items), Err(Error::Invalid(_)))); mock.answer_next_prompt(Some(&["x.txt"])); assert_eq!(host::input("Name", Some("a.txt")).unwrap().as_deref(), Some("x.txt")); + mock.answer_next_prompt(Some(&["a\nb"])); + assert_eq!(host::input_multiline("Notes", None).unwrap().as_deref(), Some("a\nb")); + mock.answer_next_prompt(None); + assert_eq!(host::input_multiline("Notes", None).unwrap(), None); mock.answer_next_prompt(Some(&[])); assert!(host::confirm("Sure?", "Really").unwrap()); mock.answer_next_prompt(None); diff --git a/sdk/rust/openidea-plugin/wit/host.wit b/sdk/rust/openidea-plugin/wit/host.wit index f6871436..3b74fc92 100644 --- a/sdk/rust/openidea-plugin/wit/host.wit +++ b/sdk/rust/openidea-plugin/wit/host.wit @@ -62,6 +62,9 @@ interface ui { /// Asks for a line of text, pre-filled with `value`; `none` when dismissed. /// Same rules as `pick`. input: func(prompt: string, value: option) -> result, error>; + /// Asks for text over several lines, pre-filled with `value`; `none` when + /// dismissed. Same rules as `input`; the dialog confirms on Cmd/Ctrl+Enter. + input-multiline: func(prompt: string, value: option) -> result, error>; /// Asks for confirmation with OK and Cancel: `true` on OK, `false` on /// Cancel or Escape. Same rules as `pick`. confirm: func(title: string, body: string) -> result; diff --git a/src/bindings.ts b/src/bindings.ts index 3bb01a27..f1a6c0de 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -6284,8 +6284,11 @@ export type ProjectStructureSettings = { excludeFiles?: string, }; -/** What a plugin asks the user (`ui.pick`, `ui.pick-many`, `ui.input`, `ui.confirm`). */ -export type Prompt = { kind: "quickPick"; title: string; items: PickItem[]; multiple: boolean } | { kind: "input"; prompt: string; value: string | null } | { kind: "confirm"; title: string; message: string }; +/** + * What a plugin asks the user (`ui.pick`, `ui.pick-many`, `ui.input`, + * `ui.input-multiline`, `ui.confirm`). + */ +export type Prompt = { kind: "quickPick"; title: string; items: PickItem[]; multiple: boolean } | { kind: "input"; prompt: string; value: string | null; multiline: boolean } | { kind: "confirm"; title: string; message: string }; export type ProxyKind = "http" | "socks"; diff --git a/src/features/plugins/__tests__/plugins.test.tsx b/src/features/plugins/__tests__/plugins.test.tsx index 343c609f..871e857a 100644 --- a/src/features/plugins/__tests__/plugins.test.tsx +++ b/src/features/plugins/__tests__/plugins.test.tsx @@ -536,13 +536,33 @@ describe("plugin prompts", () => { type: "promptRequested", pluginId: "p", promptId: "2", - prompt: { kind: "input", prompt: "Name", value: "Main" }, + prompt: { kind: "input", prompt: "Name", value: "Main", multiline: false }, }); }); await user.click(await screen.findByRole("button", { name: "Cancel" })); await waitFor(() => expect(answerPrompt).toHaveBeenCalledWith("2", null)); }); + it("asks multi-line text and confirms on Ctrl+Enter", async () => { + const user = userEvent.setup(); + const answerPrompt = vi.fn().mockResolvedValue(undefined); + setPluginsBackend({ ...createMemoryPluginsBackend(), answerPrompt }); + render(); + act(() => { + handlePluginEvent({ + type: "promptRequested", + pluginId: "p", + promptId: "5", + prompt: { kind: "input", prompt: "Notes", value: "one", multiline: true }, + }); + }); + const area = await screen.findByRole("textbox"); + expect(area.tagName).toBe("TEXTAREA"); + await user.clear(area); + await user.type(area, "a{Enter}b{Control>}{Enter}{/Control}"); + await waitFor(() => expect(answerPrompt).toHaveBeenCalledWith("5", ["a\nb"])); + }); + it("returns the typed text untrimmed", async () => { const user = userEvent.setup(); const answerPrompt = vi.fn().mockResolvedValue(undefined); @@ -553,7 +573,7 @@ describe("plugin prompts", () => { type: "promptRequested", pluginId: "p", promptId: "4", - prompt: { kind: "input", prompt: "Name", value: " Main " }, + prompt: { kind: "input", prompt: "Name", value: " Main ", multiline: false }, }); }); await user.click(await screen.findByRole("button", { name: "OK" })); @@ -569,7 +589,7 @@ describe("plugin prompts", () => { type: "promptRequested", pluginId: "p", promptId: "3", - prompt: { kind: "input", prompt: "Name", value: null }, + prompt: { kind: "input", prompt: "Name", value: null, multiline: false }, }); }); await screen.findByRole("dialog"); diff --git a/src/features/plugins/runtimeUi.tsx b/src/features/plugins/runtimeUi.tsx index 6dcd1466..d3ce5194 100644 --- a/src/features/plugins/runtimeUi.tsx +++ b/src/features/plugins/runtimeUi.tsx @@ -327,6 +327,7 @@ async function askUser(prompt: Prompt, signal: AbortSignal): Promise null, keepWhitespace: true, + multiline: prompt.multiline, signal, }); return text === null ? null : [text]; diff --git a/src/workbench/inputDialogs.module.css b/src/workbench/inputDialogs.module.css index b7106f1d..ee21eada 100644 --- a/src/workbench/inputDialogs.module.css +++ b/src/workbench/inputDialogs.module.css @@ -32,3 +32,31 @@ .activeRow { color: var(--fg-strong); } +.area { + resize: vertical; + min-height: 60px; + max-height: 240px; + padding: 4px 8px; + border: 0; + border-radius: var(--radius-control, 4px); + background: var(--bg-field, var(--bg-button)); + box-shadow: inset 0 0 0 1px var(--border-button); + color: var(--fg-strong); + font-family: inherit; + font-size: var(--fs-ui); + line-height: 20px; + outline: none; +} +.area:focus { + box-shadow: + inset 0 0 0 1px var(--focus-ring), + 0 0 0 1px var(--focus-ring); +} +.area[aria-invalid="true"] { + box-shadow: + inset 0 0 0 1px var(--danger), + 0 0 0 1px var(--danger); +} +.area::placeholder { + color: var(--fg-faint); +} diff --git a/src/workbench/inputDialogs.tsx b/src/workbench/inputDialogs.tsx index ae03df84..e48bc3d2 100644 --- a/src/workbench/inputDialogs.tsx +++ b/src/workbench/inputDialogs.tsx @@ -31,6 +31,8 @@ export interface PromptInputOptions { signal?: AbortSignal; /** Resolve with the text as typed instead of trimmed. */ keepWhitespace?: boolean; + /** A multi-line field: Enter adds a line, Cmd/Ctrl+Enter confirms. */ + multiline?: boolean; } export interface ChooseItemOptions { @@ -171,19 +173,43 @@ function PromptDialog({ options, done }: { options: PromptInputOptions; done: (v {options.label} )} - { - setTouched(true); - setValue(e.target.value); - setServerError(null); - }} - /> + {options.multiline ? ( +