aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md16
-rw-r--r--packages/bun-error/bun-error.css16
-rw-r--r--packages/bun-error/index.tsx76
-rw-r--r--src/http.zig191
-rw-r--r--src/http/mime_type.zig20
-rw-r--r--src/open.zig292
6 files changed, 594 insertions, 17 deletions
diff --git a/README.md b/README.md
index c4d406e20..6ba4b9a03 100644
--- a/README.md
+++ b/README.md
@@ -350,6 +350,22 @@ port = 5000
# When loading a .bagel file, run the JS parser
".bagel" = "js"
+[debug]
+# When navigating to a blob: or src: link, open the file in your editor
+editor = "code"
+
+# List of editors:
+# - "subl", "sublime"
+# - "vscode", "code"
+# - "textmate", "mate"
+# - "idea"
+# - "webstorm"
+# - "nvim", "neovim"
+# - "vim","vi"
+# - "emacs"
+# - "atom"
+# If you pass it a file path, it will open with the file path instead
+# It will recognize non-GUI editors, but I don't think it will work yet
```
TODO: list each property name
diff --git a/packages/bun-error/bun-error.css b/packages/bun-error/bun-error.css
index c5ed9881b..01866218e 100644
--- a/packages/bun-error/bun-error.css
+++ b/packages/bun-error/bun-error.css
@@ -21,7 +21,7 @@ a:hover {
border: inset 1px solid rgba(0, 0, 0, 0.2);
border-radius: 17px;
background-color: rgba(255, 255, 255, 0.92);
- width: 480px;
+ width: 512px;
position: fixed;
top: 120px;
@@ -166,8 +166,11 @@ a:hover {
.BunError-SourceLine-number {
font-variant: tabular-nums;
+ display: block;
+ cursor: pointer;
text-align: right;
user-select: none;
+ text-decoration: none;
-webkit-user-select: none;
}
@@ -175,6 +178,10 @@ a:hover {
color: rgb(165, 165, 165);
}
+.BunError-SourceLine-number:hover {
+ text-decoration: underline;
+}
+
.BunError-SourceLine-number,
.BunError-SourceLine-text {
height: 18px;
@@ -281,9 +288,12 @@ a:hover {
.BunError-StackFrames {
display: table;
table-layout: auto;
- padding: 13px 10px;
- margin: 8px auto;
+ padding: 16px 12px;
+ width: 100%;
+ box-sizing: border-box;
+ margin: 0 auto;
border-radius: 4px;
+ line-height: 1.2;
background-color: rgb(244, 244, 244);
}
diff --git a/packages/bun-error/index.tsx b/packages/bun-error/index.tsx
index f0943b8c1..4be467d9e 100644
--- a/packages/bun-error/index.tsx
+++ b/packages/bun-error/index.tsx
@@ -97,8 +97,38 @@ const normalizedFilename = (filename: string, cwd: string): string => {
return filename;
};
-const blobFileURL = (filename: string): string => {
- return new URL("/blob:" + filename, location.href).href;
+const blobFileURL = (
+ filename: string,
+ line?: number,
+ column?: number
+): string => {
+ var base = `/blob:${filename}`;
+ if (Number.isFinite(line)) {
+ base += `:${line}`;
+
+ if (Number.isFinite(column)) {
+ base += `:${column}`;
+ }
+ }
+
+ return new URL(base, location.href).href;
+};
+
+const openWithoutFlashOfNewTab = (event: MouseEvent) => {
+ const href = event.currentTarget.getAttribute("href");
+ if (!href || event.button !== 0) {
+ return true;
+ }
+ event.target.removeAttribute("target");
+ event.preventDefault();
+ event.nativeEvent.preventDefault();
+ event.nativeEvent.stopPropagation();
+ event.nativeEvent.stopImmediatePropagation();
+ globalThis.fetch(href + "?editor=1").then(
+ () => {},
+ (er) => {}
+ );
+ return false;
};
const srcFileURL = (filename: string, line: number, column: number): string => {
@@ -107,10 +137,10 @@ const srcFileURL = (filename: string, line: number, column: number): string => {
}
var base = `/src:${filename}`;
- if (line > -1) {
+ if (Number.isFinite(line) && line > -1) {
base = base + `:${line}`;
- if (column > -1) {
+ if (Number.isFinite(column) && column > -1) {
base = base + `:${column}`;
}
}
@@ -197,11 +227,13 @@ const SourceLines = ({
highlightColumnStart = 0,
highlightColumnEnd = Infinity,
children,
+ buildURL,
}: {
sourceLines: SourceLine[];
highlightColumnStart: number;
highlightColumnEnd: number;
highlight: number;
+ buildURL: (line?: number, column?: number) => string;
}) => {
let start = sourceLines.length;
let end = 0;
@@ -261,14 +293,17 @@ const SourceLines = ({
</div>
);
numbers[i] = (
- <div
+ <a
+ href={buildURL(line, highlightColumnStart)}
+ onClickCapture={openWithoutFlashOfNewTab}
+ target="_blank"
key={line}
className={`BunError-SourceLine-number ${
classes.empty ? "BunError-SourceLine-number--empty" : ""
} ${classes.highlight ? "BunError-SourceLine-number--highlight" : ""}`}
>
{line}
- </div>
+ </a>
);
if (classes.highlight && children) {
@@ -313,13 +348,24 @@ const SourceLines = ({
);
};
-const BuildErrorSourceLines = ({ location }: { location: Location }) => {
- const { line, line_text, column, file } = location;
+const BuildErrorSourceLines = ({
+ location,
+ filename,
+}: {
+ location: Location;
+ filename: string;
+}) => {
+ const { line, line_text, column } = location;
const sourceLines: SourceLine[] = [{ line, text: line_text }];
+ const buildURL = React.useCallback(
+ (line, column) => srcFileURL(filename, line, column),
+ [srcFileURL, filename]
+ );
return (
<SourceLines
sourceLines={sourceLines}
highlight={line}
+ buildURL={buildURL}
highlightColumnStart={column}
highlightColumnEnd={column}
/>
@@ -335,11 +381,12 @@ const BuildErrorStackTrace = ({ location }: { location: Location }) => {
<a
href={srcFileURL(filename, line, column)}
target="_blank"
+ onClickCapture={openWithoutFlashOfNewTab}
className="BunError-NativeStackTrace-filename"
>
{filename}:{line}:{column}
</a>
- <BuildErrorSourceLines location={location} />
+ <BuildErrorSourceLines filename={filename} location={location} />
</div>
);
};
@@ -414,7 +461,8 @@ const NativeStackFrame = ({
<a
target="_blank"
- href={blobFileURL(fileName)}
+ href={blobFileURL(fileName, line + 1, column)}
+ onClickCapture={openWithoutFlashOfNewTab}
className="BunError-StackFrame-link"
>
<div className="BunError-StackFrame-link-content">
@@ -451,11 +499,16 @@ const NativeStackTrace = ({
const { file = "", position } = frames[0];
const { cwd } = useContext(ErrorGroupContext);
const filename = normalizedFilename(file, cwd);
+ const buildURL = React.useCallback(
+ (line, column) => blobFileURL(file, line, column),
+ [file, blobFileURL]
+ );
return (
<div className={`BunError-NativeStackTrace`}>
<a
- href={blobFileURL(filename)}
+ href={blobFileURL(filename, position.line + 1, position.column_start)}
target="_blank"
+ onClickCapture={openWithoutFlashOfNewTab}
className="BunError-NativeStackTrace-filename"
>
{filename}:{position.line + 1}:{position.column_start}
@@ -465,6 +518,7 @@ const NativeStackTrace = ({
highlight={position.line}
sourceLines={sourceLines}
highlightColumnStart={position.column_start}
+ buildURL={buildURL}
highlightColumnEnd={position.column_stop}
>
{children}
diff --git a/src/http.zig b/src/http.zig
index 4a0fdc347..738ed9e46 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -2375,9 +2375,18 @@ pub const RequestContext = struct {
fn handleBlobURL(ctx: *RequestContext, _: *Server) !void {
var id = ctx.url.path["blob:".len..];
+ var line: string = "";
+ var column: string = "";
+
// This makes it Just Work if you pass a line/column number
if (strings.indexOfChar(id, ':')) |colon| {
+ line = id[@minimum(id.len, colon + 1)..];
id = id[0..colon];
+
+ if (strings.indexOfChar(line, ':')) |col| {
+ column = line[@minimum(line.len, col + 1)..];
+ line = line[0..col];
+ }
}
const Blob = @import("./blob.zig");
@@ -2388,12 +2397,29 @@ pub const RequestContext = struct {
if (vm.blobs.?.get(id)) |blob| {
break :brk blob;
}
+
+ if (strings.eqlComptime(id, "node_modules.server.bun")) {
+ if (vm.node_modules) |bun| {
+ if (bun.code_string) |code| {
+ break :brk Blob{ .ptr = code.str.ptr, .len = code.str.len };
+ }
+ }
+ }
}
if (JavaScript.VirtualMachine.vm_loaded) {
- if (JavaScript.VirtualMachine.vm.blobs.?.get(id)) |blob| {
+ var vm = JavaScript.VirtualMachine.vm;
+ if (vm.blobs.?.get(id)) |blob| {
break :brk blob;
}
+
+ if (strings.eqlComptime(id, "node_modules.server.bun")) {
+ if (vm.node_modules) |bun| {
+ if (bun.code_string) |code| {
+ break :brk Blob{ .ptr = code.str.ptr, .len = code.str.len };
+ }
+ }
+ }
}
return try ctx.sendNotFound();
@@ -2404,6 +2430,33 @@ pub const RequestContext = struct {
return;
}
+ const always_open = strings.contains(ctx.url.query_string, "editor");
+
+ if (always_open) {
+ const real_mime_type = MimeType.byExtension(strings.trim(std.fs.path.extension(id), "."));
+
+ if (real_mime_type.canOpenInEditor() or ctx.bundler.options.loader(std.fs.path.extension(id)) != .file) {
+ if (Server.editor == null) {
+ Server.detectEditor(ctx.bundler.env);
+ }
+
+ if (Server.editor) |editor| {
+ if (editor != .none) {
+ Server.openInEditor(editor, blob.ptr[0..blob.len], id, Fs.FileSystem.instance.tmpdir(), line, column);
+ if (Server.editor.? != .none) {
+ defer ctx.done();
+ try ctx.writeStatus(200);
+ ctx.appendHeader("Content-Type", MimeType.html.value);
+ const auto_close = "<html><body><script>window.close();</script></body></html>";
+ try ctx.prepareToSendBody(auto_close.len, false);
+ try ctx.writeBodyBuf(auto_close);
+ return;
+ }
+ }
+ }
+ }
+ }
+
defer ctx.done();
try ctx.writeStatus(200);
ctx.appendHeader("Content-Type", MimeType.text.value);
@@ -2541,9 +2594,20 @@ pub const RequestContext = struct {
fn handleSrcURL(ctx: *RequestContext, _: *Server) !void {
var input_path = ctx.url.path["src:".len..];
- while (std.mem.indexOfScalar(u8, input_path, ':')) |i| {
+ var line: string = "";
+ var column: string = "";
+ if (std.mem.indexOfScalar(u8, input_path, ':')) |i| {
+ line = input_path[i + 1 ..];
input_path = input_path[0..i];
+
+ if (line.len > 0) {
+ if (std.mem.indexOfScalar(u8, line, ':')) |j| {
+ column = line[j + 1 ..];
+ line = line[0..j];
+ }
+ }
}
+
if (input_path.len == 0) return ctx.sendNotFound();
const pathname = Fs.PathName.init(input_path);
@@ -2552,6 +2616,33 @@ pub const RequestContext = struct {
switch (result.file.value) {
.pending => |resolve_result| {
const path = resolve_result.pathConst() orelse return try ctx.sendNotFound();
+ const always_open = strings.contains(ctx.url.query_string, "editor");
+ if (always_open) {
+ if (Server.editor == null)
+ Server.detectEditor(ctx.bundler.env);
+
+ if (Server.editor) |editor| {
+ if (editor != .none) {
+ editor.open(Server.editor_path, path.text, line, column, _global.default_allocator) catch |err| {
+ if (editor != .other) {
+ Output.prettyErrorln("Error {s} opening in {s}", .{ @errorName(err), @tagName(editor) });
+ }
+
+ Server.editor = Editor.none;
+ };
+
+ if (Server.editor.? != .none) {
+ defer ctx.done();
+ try ctx.writeStatus(200);
+ ctx.appendHeader("Content-Type", MimeType.html.value);
+ const auto_close = "<html><body><script>window.close();</script></body></html>";
+ try ctx.prepareToSendBody(auto_close.len, false);
+ try ctx.writeBodyBuf(auto_close);
+ return;
+ }
+ }
+ }
+ }
var needs_close = false;
const fd = if (resolve_result.file_fd != 0)
@@ -2780,6 +2871,7 @@ var serve_as_package_path = false;
// - Resolver time
// - Parsing time
// - IO read time
+const Editor = @import("./open.zig").Editor;
pub const Server = struct {
log: logger.Log,
@@ -2791,6 +2883,99 @@ pub const Server = struct {
javascript_enabled: bool = false,
fallback_only: bool = false,
+ pub var editor: ?Editor = null;
+ pub var editor_name: string = "";
+ pub var editor_path: string = "";
+
+ pub fn openInEditor(editor_: Editor, blob: []const u8, id: string, tmpdir: std.fs.Dir, line: string, column: string) void {
+ _openInEditor(editor_, blob, id, tmpdir, line, column) catch |err| {
+ if (editor_ != .other) {
+ Output.prettyErrorln("Error {s} opening in {s}", .{ @errorName(err), @tagName(editor_) });
+ }
+
+ editor = Editor.none;
+ };
+ }
+
+ fn _openInEditor(editor_: Editor, blob: []const u8, id: string, tmpdir: std.fs.Dir, line: string, column: string) !void {
+ var basename_buf: [512]u8 = undefined;
+ var basename = std.fs.path.basename(id);
+ if (strings.endsWith(basename, ".bun") and basename.len < 499) {
+ std.mem.copy(u8, &basename_buf, basename);
+ basename_buf[basename.len..][0..3].* = ".js".*;
+ basename = basename_buf[0 .. basename.len + 3];
+ }
+
+ try tmpdir.writeFile(basename, blob);
+
+ var opened = try tmpdir.openFile(basename, .{});
+ defer opened.close();
+ var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ try editor_.open(
+ Server.editor_path,
+ try std.os.getFdPath(opened.handle, &path_buf),
+ line,
+ column,
+ default_allocator,
+ );
+ }
+
+ pub fn detectEditor(env: *DotEnv.Loader) void {
+ var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+
+ var out: string = "";
+ // first: choose from user preference
+ if (Server.editor_name.len > 0) {
+ // /usr/bin/vim
+ if (std.fs.path.isAbsolute(Server.editor_name)) {
+ Server.editor = Editor.byName(std.fs.path.basename(Server.editor_name)) orelse Editor.other;
+ editor_path = Server.editor_name;
+ return;
+ }
+
+ // "vscode"
+ if (Editor.byName(std.fs.path.basename(Server.editor_name))) |editor_| {
+ if (Editor.byPATHForEditor(env, editor_, &buf, Fs.FileSystem.instance.top_level_dir, &out)) {
+ editor = editor_;
+ editor_path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable;
+ return;
+ }
+
+ // not in path, try common ones
+ if (Editor.byFallbackPathForEditor(editor_, &out)) {
+ editor = editor_;
+ editor_path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable;
+ return;
+ }
+ }
+ }
+
+ // EDITOR=code
+ if (Editor.detect(env)) |editor_| {
+ if (Editor.byPATHForEditor(env, editor_, &buf, Fs.FileSystem.instance.top_level_dir, &out)) {
+ editor = editor_;
+ editor_path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable;
+ return;
+ }
+
+ // not in path, try common ones
+ if (Editor.byFallbackPathForEditor(editor_, &out)) {
+ editor = editor_;
+ editor_path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable;
+ return;
+ }
+ }
+
+ // Don't know, so we will just guess based on what exists
+ if (Editor.byFallback(env, &buf, Fs.FileSystem.instance.top_level_dir, &out)) |editor_| {
+ editor = editor_;
+ editor_path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable;
+ return;
+ }
+
+ Server.editor = Editor.none;
+ }
+
threadlocal var filechange_buf: [32]u8 = undefined;
threadlocal var filechange_buf_hinted: [32]u8 = undefined;
@@ -3510,6 +3695,8 @@ pub const Server = struct {
return;
}
+ Server.editor_name = debug.editor;
+
server.bundler.options.macro_remap = debug.macros orelse .{};
if (debug.fallback_only or server.bundler.env.map.get("BUN_DISABLE_BUN_JS") != null) {
diff --git a/src/http/mime_type.zig b/src/http/mime_type.zig
index 8969623b8..743560931 100644
--- a/src/http/mime_type.zig
+++ b/src/http/mime_type.zig
@@ -20,6 +20,17 @@ const MimeType = @This();
value: string,
category: Category,
+pub fn canOpenInEditor(this: MimeType) bool {
+ if (this.category == .text or this.category.isCode())
+ return true;
+
+ if (this.category == .image) {
+ return strings.eqlComptime(this.value, "image/svg+xml");
+ }
+
+ return false;
+}
+
pub const Category = enum {
image,
text,
@@ -33,6 +44,13 @@ pub const Category = enum {
javascript,
wasm,
+ pub fn isCode(this: Category) bool {
+ return switch (this) {
+ .wasm, .json, .css, .html, .javascript => true,
+ else => false,
+ };
+ }
+
pub fn isTextLike(this: Category) bool {
return switch (this) {
.javascript, .html, .text, .css, .json => true,
@@ -145,6 +163,8 @@ pub fn byExtension(ext: string) MimeType {
3 => {
const four = [4]u8{ ext[0], ext[1], ext[2], 0 };
return switch (std.mem.readIntNative(u32, &four)) {
+ Four.case("bun") => javascript,
+
Four.case("css") => css,
Four.case("jpg") => MimeType.initComptime("image/jpeg", .image),
Four.case("gif") => MimeType.initComptime("image/gif", .image),
diff --git a/src/open.zig b/src/open.zig
index 7af79457d..52062609d 100644
--- a/src/open.zig
+++ b/src/open.zig
@@ -9,7 +9,7 @@ const stringZ = _global.stringZ;
const default_allocator = _global.default_allocator;
const C = _global.C;
const std = @import("std");
-
+const DotEnv = @import("env_loader.zig");
const opener = switch (@import("builtin").target.os.tag) {
.macos => "/usr/bin/open",
.windows => "start",
@@ -32,3 +32,293 @@ pub fn openURL(url: string) !void {
_ = try child_process.wait();
return;
}
+
+pub const Editor = enum(u8) {
+ none,
+ sublime,
+ vscode,
+ atom,
+ textmate,
+ intellij,
+ webstorm,
+ vim,
+ neovim,
+ emacs,
+ other,
+
+ const StringMap = std.EnumMap(Editor, string);
+ const StringArrayMap = std.EnumMap(Editor, []const [:0]const u8);
+
+ const name_map = std.ComptimeStringMap(Editor, .{
+ .{ "sublime", Editor.sublime },
+ .{ "subl", Editor.sublime },
+ .{ "vscode", Editor.vscode },
+ .{ "code", Editor.vscode },
+ .{ "textmate", Editor.textmate },
+ .{ "mate", Editor.textmate },
+ .{ "atom", Editor.atom },
+ .{ "idea", Editor.intellij },
+ .{ "webstorm", Editor.webstorm },
+ .{ "nvim", Editor.neovim },
+ .{ "neovim", Editor.neovim },
+ .{ "vim", Editor.vim },
+ .{ "vi", Editor.vim },
+ .{ "emacs", Editor.emacs },
+ });
+
+ pub fn byName(name: string) ?Editor {
+ if (strings.indexOfChar(name, ' ')) |i| {
+ return name_map.get(name[0..i]);
+ }
+
+ return name_map.get(name);
+ }
+
+ pub fn detect(env: *DotEnv.Loader) ?Editor {
+ const vars = .{ "EDITOR", "VISUAL" };
+ inline for (vars) |name| {
+ if (env.get(name)) |value| {
+ const basename = std.fs.path.basename(value);
+ if (byName(basename)) |editor| {
+ return editor;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ const which = @import("./which.zig").which;
+ pub fn byPATH(env: *DotEnv.Loader, buf: *[std.fs.MAX_PATH_BYTES]u8, cwd: string, out: *[]const u8) ?Editor {
+ const PATH = env.get("PATH") orelse return null;
+
+ inline for (default_preference_list) |editor| {
+ if (bin_name.get(editor)) |path| {
+ if (which(buf, PATH, cwd, path)) |bin| {
+ out.* = std.mem.span(bin);
+ return editor;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ pub fn byPATHForEditor(env: *DotEnv.Loader, editor: Editor, buf: *[std.fs.MAX_PATH_BYTES]u8, cwd: string, out: *[]const u8) bool {
+ const PATH = env.get("PATH") orelse return false;
+
+ if (bin_name.get(editor)) |path| {
+ if (path.len > 0) {
+ if (which(buf, PATH, cwd, path)) |bin| {
+ out.* = std.mem.span(bin);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ pub fn byFallbackPathForEditor(editor: Editor, out: ?*[]const u8) bool {
+ if (bin_path.get(editor)) |paths| {
+ for (paths) |path| {
+ if (std.os.open(path, 0, 0)) |opened| {
+ std.os.close(opened);
+ if (out != null) {
+ out.?.* = std.mem.span(path);
+ }
+ return true;
+ } else |_| {}
+ }
+ }
+
+ return false;
+ }
+
+ pub fn byFallback(env: *DotEnv.Loader, buf: *[std.fs.MAX_PATH_BYTES]u8, cwd: string, out: *[]const u8) ?Editor {
+ inline for (default_preference_list) |editor| {
+ if (byPATHForEditor(env, editor, buf, cwd, out)) {
+ return editor;
+ }
+
+ if (byFallbackPathForEditor(editor, out)) {
+ return editor;
+ }
+ }
+
+ return null;
+ }
+
+ pub const default_preference_list = [_]Editor{
+ .vscode,
+ .sublime,
+ .atom,
+ .neovim,
+
+ .webstorm,
+ .intellij,
+ .textmate,
+ .vim,
+ };
+
+ pub const bin_name: StringMap = brk: {
+ var map = StringMap{};
+
+ map.put(.sublime, "subl");
+ map.put(.vscode, "code");
+ map.put(.atom, "atom");
+ map.put(.textmate, "mate");
+ map.put(.intellij, "idea");
+ map.put(.webstorm, "webstorm");
+ map.put(.vim, "vim");
+ map.put(.neovim, "nvim");
+ map.put(.emacs, "emacs");
+ map.put(.other, "");
+ break :brk map;
+ };
+
+ pub const bin_path: StringArrayMap = brk: {
+ var map = StringArrayMap{};
+
+ if (Environment.isMac) {
+ map.put(.vscode, &.{
+ "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
+ "/Applications/VSCodium.app/Contents/Resources/app/bin/code",
+ });
+ map.put(
+ .atom,
+ &.{
+ "/Applications/Atom.app/Contents/Resources/app/atom.sh",
+ },
+ );
+ map.put(
+ .sublime,
+ &.{
+ "/Applications/Sublime Text 3.app/Contents/SharedSupport/bin/subl",
+ "/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl",
+ "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl",
+ },
+ );
+ }
+
+ break :brk map;
+ };
+
+ pub fn isJetBrains(editor: Editor) bool {
+ return switch (editor) {
+ .intellij, .webstorm => true,
+ else => false,
+ };
+ }
+
+ pub fn open(
+ editor: Editor,
+ binary: string,
+ file: []const u8,
+ line: ?string,
+ column: ?string,
+ allocator: std.mem.Allocator,
+ ) !void {
+ var file_path_buf: [std.fs.MAX_PATH_BYTES + 1024]u8 = undefined;
+ var file_path_buf_stream = std.io.fixedBufferStream(&file_path_buf);
+ var file_path_buf_writer = file_path_buf_stream.writer();
+ var args_buf: [10]string = undefined;
+
+ var i: usize = 0;
+
+ if (editor == .vim or editor == .emacs or editor == .neovim) {
+ args_buf[0] = opener;
+ i += 1;
+
+ args_buf[i] = binary;
+ i += 1;
+
+ if (Environment.isMac) {
+ args_buf[i] = "--args";
+ i += 1;
+ }
+ }
+
+ args_buf[i] = binary;
+ i += 1;
+
+ if (editor == .vscode and line != null and line.?.len > 0) {
+ args_buf[i] = "--goto";
+
+ i += 1;
+ }
+
+ switch (editor) {
+ .sublime, .atom, .vscode, .webstorm, .intellij => {
+ try file_path_buf_writer.writeAll(file);
+ if (line) |line_| {
+ if (line_.len > 0) {
+ try file_path_buf_writer.print(":{s}", .{line_});
+
+ if (!editor.isJetBrains()) {
+ if (column) |col| {
+ if (col.len > 0)
+ try file_path_buf_writer.print(":{s}", .{col});
+ }
+ }
+ }
+ }
+ if (file_path_buf_stream.pos > 0) {
+ args_buf[i] = file_path_buf_stream.getWritten();
+ i += 1;
+ }
+ },
+ .textmate => {
+ try file_path_buf_writer.writeAll(file);
+ var file_path = file_path_buf_stream.getWritten();
+
+ if (line) |line_| {
+ if (line_.len > 0) {
+ args_buf[i] = "--line";
+ i += 1;
+
+ try file_path_buf_writer.print("{s}", .{line_});
+
+ if (column) |col| {
+ if (col.len > 0)
+ try file_path_buf_writer.print(":{s}", .{col});
+ }
+
+ var line_column = file_path_buf_stream.getWritten()[file_path.len..];
+ if (line_column.len > 0) {
+ args_buf[i] = line_column;
+ i += 1;
+ }
+ }
+ }
+
+ if (file_path_buf_stream.pos > 0) {
+ args_buf[i] = file_path;
+ i += 1;
+ }
+ },
+ else => {
+ if (file.len > 0) {
+ try file_path_buf_writer.writeAll(file);
+ var file_path = file_path_buf_stream.getWritten();
+ args_buf[i] = file_path;
+ i += 1;
+ }
+ },
+ }
+
+ var child_process = try std.ChildProcess.init(args_buf[0..i], allocator);
+ child_process.stderr_behavior = .Pipe;
+ child_process.stdin_behavior = .Ignore;
+ child_process.stdout_behavior = .Pipe;
+ try child_process.spawn();
+ var thread = try std.Thread.spawn(.{}, autoClose, .{child_process});
+ thread.detach();
+ }
+
+ fn autoClose(child_process: *std.ChildProcess) void {
+ Global.setThreadName("Open Editor");
+ _ = child_process.wait() catch {};
+ child_process.deinit();
+ }
+};