feat(http): convert requests to and from cURL, add environment files (#2592)

Convert to cURL and Copy copies the request at the caret as a cURL command with
the selected environment applied; pasting a `curl ...` command into a .http file
inserts the request it sends (headers, data variants, --json, -F multipart, -u,
-b, -A, -e, -G, POSIX and Windows line continuations). Add Environment to
Public / Private File opens the env file that applies to the request file,
creating it next to it when missing. The conversion lives in ide-http and
shares request preparation with running.

Refs #2435
This commit is contained in:
l0ng-ai
2026-11-11 04:59:34 +08:00
committed by GitHub
parent 3b7d72bd03
commit d2b0bd6e75
11 changed files with 553 additions and 10 deletions
+294
View File
@@ -0,0 +1,294 @@
//! cURL ⇄ `.http` request: Convert to cURL and Copy, and the conversion of a pasted
//! `curl …` command into a request.
use crate::PreparedRequest;
/// `req` as a cURL command line, one option per line (POSIX shell quoting).
pub fn to_curl(req: &PreparedRequest) -> String {
let mut parts = vec![format!("curl -X {} {}", req.method, quote(&req.url))];
for (k, v) in &req.headers {
parts.push(format!("-H {}", quote(&format!("{k}: {v}"))));
}
if let Some(body) = &req.body {
parts.push(format!("--data-raw {}", quote(body)));
}
parts.join(" \\\n ")
}
/// `s` in single quotes, as POSIX shells read it.
fn quote(s: &str) -> String {
format!("'{}'", s.replace('\'', r"'\''"))
}
/// The request a `curl …` command sends, as `.http` text; `None` when `cmd` is not a cURL
/// command or names no URL.
pub fn from_curl(cmd: &str) -> Option<String> {
let words = split_words(cmd)?;
let mut args = words.iter().map(String::as_str);
if args.next() != Some("curl") {
return None;
}
let mut method: Option<String> = None;
let mut url: Option<String> = None;
let mut headers: Vec<(String, String)> = Vec::new();
let mut data: Vec<String> = Vec::new();
let mut form: Vec<(String, String)> = Vec::new();
let mut get = false;
let header = |headers: &mut Vec<(String, String)>, line: &str| {
if let Some((k, v)) = line.split_once(':') {
headers.push((k.trim().to_owned(), v.trim().to_owned()));
}
};
while let Some(arg) = args.next() {
// `--opt=value` and `-Xvalue` forms.
let (flag, inline) = match arg.split_once('=') {
Some((f, v)) if arg.starts_with("--") => (f, Some(v.to_owned())),
_ if arg.len() > 2 && arg.starts_with('-') && !arg.starts_with("--") && takes_value(&arg[..2]) => {
(&arg[..2], Some(arg[2..].to_owned()))
}
_ => (arg, None),
};
let mut value = || inline.clone().or_else(|| args.next().map(str::to_owned));
match flag {
"-X" | "--request" => method = value(),
"-H" | "--header" => header(&mut headers, &value()?),
"-d" | "--data" | "--data-raw" | "--data-binary" | "--data-ascii" | "--data-urlencode" => {
data.push(value()?)
}
"--json" => {
data.push(value()?);
headers.push(("Content-Type".into(), "application/json".into()));
headers.push(("Accept".into(), "application/json".into()));
}
"-F" | "--form" | "--form-string" => {
let v = value()?;
let (k, v) = v.split_once('=')?;
form.push((k.to_owned(), v.to_owned()));
}
"-u" | "--user" => {
let v = value()?;
let (user, pass) = v.split_once(':').unwrap_or((&v, ""));
headers.push(("Authorization".into(), format!("Basic {user} {pass}").trim_end().to_owned()));
}
"-b" | "--cookie" => headers.push(("Cookie".into(), value()?)),
"-A" | "--user-agent" => headers.push(("User-Agent".into(), value()?)),
"-e" | "--referer" => headers.push(("Referer".into(), value()?)),
"--url" => url = value(),
"-G" | "--get" => get = true,
f if f.starts_with('-') => {
// Options with a value the request doesn't need (timeouts, output, proxy…).
if takes_value(f) {
value();
}
}
u => {
if url.is_none() {
url = Some(u.to_owned());
}
}
}
}
let mut url = url?;
if get && !data.is_empty() {
url.push(if url.contains('?') { '&' } else { '?' });
url.push_str(&data.join("&"));
data.clear();
}
let method = method.unwrap_or_else(|| if data.is_empty() && form.is_empty() { "GET" } else { "POST" }.into());
let mut body = None;
if !form.is_empty() {
const BOUNDARY: &str = "WebAppBoundary";
headers.retain(|(k, _)| !k.eq_ignore_ascii_case("content-type"));
headers.push(("Content-Type".into(), format!("multipart/form-data; boundary={BOUNDARY}")));
let mut text = String::new();
for (name, value) in &form {
text.push_str(&format!("--{BOUNDARY}\n"));
match value.strip_prefix('@') {
Some(file) => {
let file = file.split(';').next().unwrap_or(file);
let base = file.rsplit(['/', '\\']).next().unwrap_or(file);
text.push_str(&format!(
"Content-Disposition: form-data; name=\"{name}\"; filename=\"{base}\"\n\n< {file}\n"
));
}
None => text.push_str(&format!("Content-Disposition: form-data; name=\"{name}\"\n\n{value}\n")),
}
}
text.push_str(&format!("--{BOUNDARY}--"));
body = Some(text);
} else if !data.is_empty() {
if !headers.iter().any(|(k, _)| k.eq_ignore_ascii_case("content-type")) {
headers.push(("Content-Type".into(), "application/x-www-form-urlencoded".into()));
}
body = Some(data.join("&"));
}
let mut out = format!("{method} {url}\n");
for (k, v) in &headers {
out.push_str(&format!("{k}: {v}\n"));
}
if let Some(body) = body {
out.push('\n');
out.push_str(&body);
out.push('\n');
}
Some(out)
}
/// Options that take a value, so it isn't read as the URL.
fn takes_value(flag: &str) -> bool {
matches!(
flag,
"-X" | "-H"
| "-d"
| "-F"
| "-u"
| "-b"
| "-A"
| "-e"
| "-o"
| "-x"
| "-m"
| "-c"
| "-E"
| "-T"
| "-w"
| "-r"
| "-K"
| "--output"
| "--proxy"
| "--max-time"
| "--connect-timeout"
| "--cookie-jar"
| "--cert"
| "--key"
| "--cacert"
| "--upload-file"
| "--write-out"
| "--range"
| "--config"
| "--retry"
| "--resolve"
| "--interface"
)
}
/// Shell words of `cmd`: single and double quotes, backslash escapes and line continuations
/// (also `^` / backtick continuations from Windows shells). `None` for an unclosed quote.
fn split_words(cmd: &str) -> Option<Vec<String>> {
let mut words = Vec::new();
let mut cur = String::new();
let mut in_word = false;
let mut chars = cmd.trim().chars().peekable();
while let Some(c) = chars.next() {
match c {
'\'' => {
in_word = true;
loop {
match chars.next()? {
'\'' => break,
c => cur.push(c),
}
}
}
'"' => {
in_word = true;
loop {
match chars.next()? {
'"' => break,
'\\' if matches!(chars.peek(), Some('"' | '\\' | '$' | '`')) => cur.push(chars.next()?),
c => cur.push(c),
}
}
}
'\\' | '^' | '`' if matches!(chars.peek(), Some('\n' | '\r')) => {
while matches!(chars.peek(), Some('\n' | '\r')) {
chars.next();
}
}
'\\' => {
in_word = true;
if let Some(n) = chars.next() {
cur.push(n);
}
}
c if c.is_whitespace() => {
if in_word {
words.push(std::mem::take(&mut cur));
in_word = false;
}
}
c => {
in_word = true;
cur.push(c);
}
}
}
if in_word {
words.push(cur);
}
Some(words)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_prepared_request_becomes_a_quoted_curl_command() {
let req = PreparedRequest {
name: None,
method: "POST".into(),
url: "https://api.example.com/users?q=a b".into(),
headers: vec![("Content-Type".into(), "application/json".into())],
body: Some("{\"name\": \"O'Brien\"}".into()),
};
assert_eq!(
to_curl(&req),
"curl -X POST 'https://api.example.com/users?q=a b' \\\n -H 'Content-Type: application/json' \\\n --data-raw '{\"name\": \"O'\\''Brien\"}'"
);
// And back.
let http = from_curl(&to_curl(&req)).unwrap();
assert_eq!(
http,
"POST https://api.example.com/users?q=a b\nContent-Type: application/json\n\n{\"name\": \"O'Brien\"}\n"
);
}
#[test]
fn a_browser_copied_command_becomes_a_request() {
let cmd = r#"curl 'https://example.com/api/items' \
-H 'accept: application/json' \
-H "authorization: Bearer abc" \
--data-raw '{"a":1}' \
--compressed -k -s"#;
assert_eq!(
from_curl(cmd).unwrap(),
"POST https://example.com/api/items\naccept: application/json\nauthorization: Bearer abc\nContent-Type: application/x-www-form-urlencoded\n\n{\"a\":1}\n"
);
}
#[test]
fn options_map_to_headers_query_and_multipart() {
assert_eq!(
from_curl("curl -G https://x.dev/s -d q=1 -d page=2 -u bob:secret -A agent --max-time 5").unwrap(),
"GET https://x.dev/s?q=1&page=2\nAuthorization: Basic bob secret\nUser-Agent: agent\n"
);
assert_eq!(
from_curl("curl -XPUT --url=https://x.dev/a --json '{}'").unwrap(),
"PUT https://x.dev/a\nContent-Type: application/json\nAccept: application/json\n\n{}\n"
);
assert_eq!(
from_curl("curl https://x.dev/up -F name=doc -F file=@/tmp/a.txt").unwrap(),
"POST https://x.dev/up\nContent-Type: multipart/form-data; boundary=WebAppBoundary\n\n--WebAppBoundary\nContent-Disposition: form-data; name=\"name\"\n\ndoc\n--WebAppBoundary\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\n\n< /tmp/a.txt\n--WebAppBoundary--\n"
);
// Windows `cmd` continuation.
assert_eq!(from_curl("curl ^\n \"https://x.dev\"").unwrap(), "GET https://x.dev\n");
}
#[test]
fn other_text_is_not_a_curl_command() {
assert_eq!(from_curl("GET https://x.dev"), None);
assert_eq!(from_curl("curl -H 'a: b'"), None, "no URL");
assert_eq!(from_curl("curl 'https://x.dev"), None, "unclosed quote");
}
}
+28
View File
@@ -86,6 +86,20 @@ fn read_first(dirs: &[PathBuf], name: &str) -> IdeResult<EnvMap> {
Ok(EnvMap::new())
}
/// The public (or private) environment file that applies to `file`: the nearest existing one,
/// else a new one next to `file` holding a sample environment to fill in.
pub fn environment_file(file: &Path, root: Option<&Path>, private: bool) -> IdeResult<PathBuf> {
let name = if private { PRIVATE_FILE } else { PUBLIC_FILE };
if let Some(found) = search_dirs(Some(file), root).into_iter().map(|d| d.join(name)).find(|p| p.is_file()) {
return Ok(found);
}
let dir = file.parent().ok_or_else(|| IdeError::invalid("the request file has no folder"))?;
let path = dir.join(name);
std::fs::write(&path, "{\n \"dev\": {\n \"name\": \"value\"\n }\n}\n")
.map_err(|e| IdeError::internal(format!("cannot create {}: {e}", path.display())))?;
Ok(path)
}
impl Environments {
pub fn load(file: Option<&Path>, root: Option<&Path>) -> IdeResult<Self> {
let dirs = search_dirs(file, root);
@@ -272,4 +286,18 @@ mod tests {
let envs = Environments::load(Some(&tmp.path().join("a.http")), Some(tmp.path())).unwrap();
assert_eq!(envs.names(), vec!["dev"]);
}
#[test]
fn an_environment_file_is_found_up_the_tree_or_created_next_to_the_request() {
let tmp = tempfile::tempdir().unwrap();
let api = tmp.path().join("api");
std::fs::create_dir_all(&api).unwrap();
let request = api.join("a.http");
std::fs::write(tmp.path().join(PUBLIC_FILE), "{}").unwrap();
assert_eq!(environment_file(&request, Some(tmp.path()), false).unwrap(), tmp.path().join(PUBLIC_FILE));
let private = environment_file(&request, Some(tmp.path()), true).unwrap();
assert_eq!(private, api.join(PRIVATE_FILE));
let envs = Environments::load(Some(&request), Some(tmp.path())).unwrap();
assert_eq!(envs.names(), vec!["dev"], "the new file is a valid environment file");
}
}
+18 -7
View File
@@ -6,6 +6,7 @@
//! of the backend, with the proxy of System Settings › HTTP Proxy.
pub mod client;
pub mod curl;
pub mod env;
pub mod exec;
pub mod history;
@@ -32,13 +33,7 @@ pub struct RunInput<'a> {
/// Prepares and sends request `index` of `text`. Preparation errors (bad
/// variables, missing includes) fail; transport errors are part of the exchange.
pub async fn run(input: RunInput<'_>, cancel: &CancellationToken) -> IdeResult<HttpExchange> {
let parsed = parse::parse(input.text);
let raw = parsed
.requests
.get(input.index as usize)
.ok_or_else(|| IdeError::not_found(format!("no request #{}", input.index + 1)))?;
let envs = Environments::load(input.file, input.root)?;
let request = exec::prepare(raw, &parsed.variables, &envs, input.env, input.file.and_then(Path::parent))?;
let request = prepare(&input)?;
let time_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs_f64() * 1000.0)
@@ -70,6 +65,22 @@ pub async fn run(input: RunInput<'_>, cancel: &CancellationToken) -> IdeResult<H
})
}
/// Request `index` of `text` with its variables substituted and includes read.
pub fn prepare(input: &RunInput<'_>) -> IdeResult<PreparedRequest> {
let parsed = parse::parse(input.text);
let raw = parsed
.requests
.get(input.index as usize)
.ok_or_else(|| IdeError::not_found(format!("no request #{}", input.index + 1)))?;
let envs = Environments::load(input.file, input.root)?;
exec::prepare(raw, &parsed.variables, &envs, input.env, input.file.and_then(Path::parent))
}
/// Convert to cURL and Copy: request `index` of `text`, resolved, as a cURL command.
pub fn curl(input: &RunInput<'_>) -> IdeResult<String> {
prepare(input).map(|r| curl::to_curl(&r))
}
/// Environment names available to `file`.
pub fn environments(file: Option<&Path>, root: Option<&Path>) -> IdeResult<Vec<String>> {
Ok(Environments::load(file, root)?.names())
+51
View File
@@ -84,3 +84,54 @@ pub fn http_history_clear(window: Window, state: State<'_, AppState>) -> IdeResu
pub async fn proxy_check_connection(proxy: ide_core::settings::ProxySettings, url: String) -> IdeResult<String> {
ide_http::client::check_connection(&proxy, &url, &ide_core::CancellationToken::new()).await
}
/// Convert to cURL and Copy: request `index` of `text` (the editor buffer of `path`), with
/// the variables of `env` substituted, as a cURL command.
#[tauri::command]
#[specta::specta]
pub async fn http_curl(
window: Window,
state: State<'_, AppState>,
path: Option<IpcPath>,
text: String,
index: u32,
env: Option<String>,
) -> IdeResult<String> {
let root = state.project(window.label()).ok().map(|p| p.root.clone());
tauri::async_runtime::spawn_blocking(move || {
ide_http::curl(&ide_http::RunInput {
text: &text,
index,
file: path.as_deref(),
root: root.as_deref(),
env: env.as_deref(),
})
})
.await
.map_err(|e| IdeError::internal(e.to_string()))?
}
/// A pasted `curl …` command as `.http` request text; `None` when it isn't one.
#[tauri::command]
#[specta::specta]
pub fn http_from_curl(command: String) -> Option<String> {
ide_http::curl::from_curl(&command)
}
/// The environment file for the request file `path` (`is_private`: the private one): the nearest
/// existing one, else a new one next to it.
#[tauri::command]
#[specta::specta]
pub async fn http_environment_file(
window: Window,
state: State<'_, AppState>,
path: IpcPath,
is_private: bool,
) -> IdeResult<IpcPath> {
let root = state.project(window.label()).ok().map(|p| p.root.clone());
tauri::async_runtime::spawn_blocking(move || {
ide_http::env::environment_file(&path, root.as_deref(), is_private).map(IpcPath::from)
})
.await
.map_err(|e| IdeError::internal(e.to_string()))?
}
+3
View File
@@ -603,6 +603,9 @@ pub fn builder() -> tauri_specta::Builder<tauri::Wry> {
http::http_requests,
http::http_environments,
http::http_execute,
http::http_curl,
http::http_from_curl,
http::http_environment_file,
http::http_history,
http::http_history_get,
http::http_history_clear,
+12
View File
@@ -1369,6 +1369,18 @@ export const commands = {
* the exchange in the response history. Cancel with `cancel_request(request)`.
*/
httpExecute: (request: RequestId, path: string | null, text: string, index: number, env: string | null) => typedError<HttpExchange, IdeError>(__TAURI_INVOKE("http_execute", { request, path, text, index, env })),
/**
* Convert to cURL and Copy: request `index` of `text` (the editor buffer of `path`), with
* the variables of `env` substituted, as a cURL command.
*/
httpCurl: (path: string | null, text: string, index: number, env: string | null) => typedError<string, IdeError>(__TAURI_INVOKE("http_curl", { path, text, index, env })),
/** A pasted `curl …` command as `.http` request text; `None` when it isn't one. */
httpFromCurl: (command: string) => __TAURI_INVOKE<string | null>("http_from_curl", { command }),
/**
* The environment file for the request file `path` (`is_private`: the private one): the nearest
* existing one, else a new one next to it.
*/
httpEnvironmentFile: (path: IpcPath, isPrivate: boolean) => typedError<IpcPath, IdeError>(__TAURI_INVOKE("http_environment_file", { path, isPrivate })),
httpHistory: () => typedError<HttpHistoryEntry[], IdeError>(__TAURI_INVOKE("http_history")),
httpHistoryGet: (id: string) => typedError<HttpExchange, IdeError>(__TAURI_INVOKE("http_history_get", { id })),
httpHistoryClear: () => typedError<null, IdeError>(__TAURI_INVOKE("http_history_clear")),
+44 -1
View File
@@ -11,11 +11,13 @@ import type { EditorDocContext } from "@/editor/api";
import { shortcutLabel } from "@/platform/keymap";
import { isVirtualUri } from "@/platform/virtualDocs";
import type { HttpRequestBlock } from "@/platform/ipc";
import { IconButton, Select, gutterIconMarkup, T } from "@/ui";
import { executeAction } from "@/platform/actions";
import { IconButton, Select, gutterIconMarkup, showContextMenu, T } from "@/ui";
import {
cancelRunning,
loadEnvironments,
parseRequests,
requestFromCurl,
runAll,
runRequest,
setEnvironment,
@@ -129,6 +131,27 @@ function Toolbar({ path, view }: { path: string | null; view: EditorView }) {
disabled={!path}
onClick={() => path && void loadEnvironments(path)}
/>
<IconButton
icon="plus"
title="Add Environment File"
disabled={!path}
onClick={(e) => {
const r = e.currentTarget.getBoundingClientRect();
showContextMenu({ x: r.left, y: r.bottom }, [
{ kind: "action", action: "http.addPublicEnvironment" },
{ kind: "action", action: "http.addPrivateEnvironment" },
]);
}}
/>
<IconButton
icon="copy"
title="Convert to cURL and Copy"
shortcut={shortcutLabel("http.copyAsCurl")}
onClick={() => {
view.focus();
void executeAction("http.copyAsCurl");
}}
/>
</div>
);
}
@@ -144,6 +167,25 @@ function toolbarPanel(path: string | null) {
const DEBOUNCE = 300;
const CURL = /^\s*curl\s/;
/** A pasted `curl …` command becomes the request it sends. */
const curlPaste = EditorView.domEventHandlers({
paste(event, view) {
const text = event.clipboardData?.getData("text/plain");
if (!text || !CURL.test(text) || view.state.readOnly) return false;
event.preventDefault();
const { doc } = view.state;
void requestFromCurl(text)
.catch(() => null)
.then((request) => {
if (view.state.doc !== doc) return;
view.dispatch(view.state.replaceSelection(request ?? text), { scrollIntoView: true, userEvent: "input.paste" });
});
return true;
},
});
export function httpEditorExtension(ctx: EditorDocContext): Extension {
if (ctx.language !== HTTP_LANGUAGE) return [];
const path = ctx.virtual || isVirtualUri(ctx.path) ? null : ctx.path;
@@ -170,6 +212,7 @@ export function httpEditorExtension(ctx: EditorDocContext): Extension {
return [
requestsField,
detector,
curlPaste,
showPanel.of(toolbarPanel(path)),
gutter({
class: `${styles.runGutter} cm-openidea-gutter-http`,
+46 -1
View File
@@ -20,7 +20,7 @@ import {
import { HTTP_LANGUAGE, httpEditorExtension, refreshRequests, requestAtCaret } from "./editor";
import { HttpClientHeaderActions, HttpClientView } from "./HttpClientView";
import { httpLanguageDescription } from "./language";
import { SERVICE_VIEW, resetHttpClient, runAll, runRequest } from "./store";
import { SERVICE_VIEW, copyAsCurl, openEnvironmentFile, resetHttpClient, runAll, runRequest } from "./store";
import {
GENERATE_CONTROLLER_REQUESTS,
GENERATE_REQUEST,
@@ -65,6 +65,24 @@ async function runAtCaret() {
}
/** New › HTTP Request in `directory`: the name prompt of the file templates (features/templates), with the example body. */
async function copyAtCaret() {
const e = activeHttpEditor();
if (!e) return;
await refreshRequests(e.view);
const r = requestAtCaret(e.view);
if (!r) {
notifications().notify({ level: "info", title: "No HTTP request at the caret", key: "http-run" });
return;
}
await copyAsCurl(filePath(e.ctx.path, e.ctx.virtual), e.view.state.doc.toString(), r.index);
}
function addEnvironment(isPrivate: boolean) {
const e = activeHttpEditor();
const path = e && filePath(e.ctx.path, e.ctx.virtual);
if (path) void openEnvironmentFile(path, isPrivate);
}
function newRequestArg(directory: string) {
return {
directory,
@@ -158,6 +176,33 @@ export function activate(): () => void {
when: "editorTextFocus && editorLangId == http",
run: () => runAtCaret(),
},
{
id: "http.copyAsCurl",
title: "Convert to cURL and Copy",
category: "Tools",
icon: "copy",
keywords: ["curl", "http client", "copy"],
when: "editorLangId == http",
run: () => copyAtCaret(),
},
{
id: "http.addPublicEnvironment",
title: "Add Environment to Public File",
description: "Opens http-client.env.json for this request file, creating it if missing",
category: "Tools",
keywords: ["http client", "environment", "env.json"],
when: "editorLangId == http",
run: () => addEnvironment(false),
},
{
id: "http.addPrivateEnvironment",
title: "Add Environment to Private File",
description: "Opens http-client.private.env.json for this request file, creating it if missing",
category: "Tools",
keywords: ["http client", "environment", "env.json", "secrets"],
when: "editorLangId == http",
run: () => addEnvironment(true),
},
{
id: "http.runAll",
title: "Run All HTTP Requests in File",
+39 -1
View File
@@ -10,7 +10,8 @@ import {
type HttpRequestBlock,
type RequestId,
} from "@/platform/ipc";
import { notifications, showServiceView } from "@/workbench/contributions";
import { writeClipboardText } from "@/platform/clipboard";
import { editorService, notifications, showServiceView, showStatusInfo } from "@/workbench/contributions";
export const SERVICE_VIEW = "httpClient";
const ENV_KEY = "openidea.httpClient.environments";
@@ -158,6 +159,43 @@ export async function runRequest(path: string | null, text: string, index: numbe
}
}
/** Convert to cURL and Copy: request `index` of `text`, with the file's environment applied. */
export async function copyAsCurl(path: string | null, text: string, index: number): Promise<void> {
try {
if (path && !useHttpClient.getState().environments[path]) await loadEnvironments(path);
await writeClipboardText(await call(commands.httpCurl(path, text, index, effectiveEnvironment(path))));
showStatusInfo("The request was copied as a cURL command");
} catch (e) {
notifications().notify({
level: "error",
title: "Cannot convert the request to cURL",
body: e instanceof Error ? e.message : String(e),
key: "http-curl",
});
}
}
/** A pasted `curl …` command as request text; null when it isn't one. */
export function requestFromCurl(command: string): Promise<string | null> {
return commands.httpFromCurl(command);
}
/** Add Environment to Public / Private File: opens the env file that applies to `path`, created if missing. */
export async function openEnvironmentFile(path: string, isPrivate: boolean): Promise<void> {
try {
const file = await call(commands.httpEnvironmentFile(path, isPrivate));
await editorService().openFile(file, { focus: true });
void loadEnvironments(path);
} catch (e) {
notifications().notify({
level: "error",
title: "Cannot create the environment file",
body: e instanceof Error ? e.message : String(e),
key: "http-env-file",
});
}
}
/** Runs every request of the file in order; stops when one is cancelled. */
export async function runAll(path: string | null, text: string): Promise<void> {
const blocks = await parseRequests(text);
+9
View File
@@ -1,4 +1,13 @@
{
"Add Environment File": "Add Environment File",
"Convert to cURL and Copy": "Convert to cURL and Copy",
"Add Environment to Public File": "Add Environment to Public File",
"Add Environment to Private File": "Add Environment to Private File",
"Opens http-client.env.json for this request file, creating it if missing": "Opens http-client.env.json for this request file, creating it if missing",
"Opens http-client.private.env.json for this request file, creating it if missing": "Opens http-client.private.env.json for this request file, creating it if missing",
"Cannot convert the request to cURL": "Cannot convert the request to cURL",
"Cannot create the environment file": "Cannot create the environment file",
"The request was copied as a cURL command": "The request was copied as a cURL command",
"Generate Sources and Update Folders For All Projects": "Generate Sources and Update Folders For All Projects",
"Generate Sources and Update Folders": "Generate Sources and Update Folders",
"Runs generate-sources, then reloads the Maven projects so generated folders become source folders": "Runs generate-sources, then reloads the Maven projects so generated folders become source folders",
+9
View File
@@ -1,4 +1,13 @@
{
"Add Environment File": "添加环境文件",
"Convert to cURL and Copy": "转换为 cURL 并复制",
"Add Environment to Public File": "将环境添加到公共文件",
"Add Environment to Private File": "将环境添加到私有文件",
"Opens http-client.env.json for this request file, creating it if missing": "打开此请求文件对应的 http-client.env.json,不存在时创建",
"Opens http-client.private.env.json for this request file, creating it if missing": "打开此请求文件对应的 http-client.private.env.json,不存在时创建",
"Cannot convert the request to cURL": "无法将请求转换为 cURL",
"Cannot create the environment file": "无法创建环境文件",
"The request was copied as a cURL command": "已将请求复制为 cURL 命令",
"Related targets": "相关目标",
"GDB (C, C++)": "GDB(C、C++)",
"Unknown error": "未知错误",