rush

created pr with 128.1 on 2026-06-18T17:34:30Z · by c8ef7d19
cmds
checkout latest patchset:
ssh pr.pico.sh print 128 | git am -3
checkout any patchset in a patch request:
ssh pr.pico.sh print 128.[rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 128

Patchset 128.1 on 2026-06-18T17:34:30Z · commit 39e3783

+13 -6 src/completion.zig #
......@@ -11,6 +11,7 @@ const runner = @import("runner.zig");
1111 const runtime = @import("runtime.zig");
1212 const shell_state_mod = @import("shell/state.zig");
1313 const editor_completion = @import("editor/completion.zig");
14+const expand = @import("shell/expand.zig");
1415
1516 pub const CancellationToken = editor_completion.CancellationToken;
1617 pub const Kind = editor_completion.Kind;
......@@ -1658,6 +1659,7 @@ fn appendProviderCandidates(
16581659 semantic.replace_start,
16591660 semantic.replace_end,
16601661 .{},
1662+ shell_state.envLookup(),
16611663 );
16621664 defer freeCandidates(allocator, candidates);
16631665 for (candidates) |candidate| try builder.appendCandidateIfMissing(allocator, candidate);
......@@ -1670,6 +1672,7 @@ fn appendProviderCandidates(
16701672 semantic.replace_start,
16711673 semantic.replace_end,
16721674 .{ .directories_only = true },
1675+ shell_state.envLookup(),
16731676 );
16741677 defer freeCandidates(allocator, candidates);
16751678 for (candidates) |candidate| try builder.appendCandidateIfMissing(allocator, candidate);
......@@ -1735,6 +1738,7 @@ fn appendFunctionProviderCandidates(
17351738 value_position,
17361739 parsed_options,
17371740 operands,
1741+ shell_state.envLookup(),
17381742 );
17391743 defer provider_state.deinit();
17401744
......@@ -2241,7 +2245,7 @@ pub fn defaultPathApplication(
22412245 const word = try decodeShellCompletionSlice(allocator, source, replace_start, replace_end);
22422246 defer allocator.free(word);
22432247
2244- const candidates = try pathCandidates(allocator, io, word, replace_start, replace_end);
2248+ const candidates = try pathCandidates(allocator, io, word, replace_start, replace_end, .{});
22452249 defer freeCandidates(allocator, candidates);
22462250 return applyCandidatesForInputWithPolicy(allocator, source, candidates, .prefixOnly());
22472251 }
......@@ -2270,7 +2274,7 @@ pub fn defaultApplication(
22702274 const candidates = if (context.kind == .command and std.mem.indexOfScalar(u8, word, '/') == null)
22712275 try commandCandidates(allocator, io, shell_state, replace_start, replace_end)
22722276 else
2273- try pathCandidates(allocator, io, word, replace_start, replace_end);
2277+ try pathCandidates(allocator, io, word, replace_start, replace_end, shell_state.envLookup());
22742278 defer freeCandidates(allocator, candidates);
22752279 return applyCandidatesForInputWithPolicy(allocator, source, candidates, .prefixOnly());
22762280 }
......@@ -2296,8 +2300,9 @@ fn pathCandidates(
22962300 word: []const u8,
22972301 replace_start: usize,
22982302 replace_end: usize,
2303+ env: expand.EnvLookup,
22992304 ) ![]Candidate {
2300- return pathCandidatesWithOptions(allocator, io, word, replace_start, replace_end, .{});
2305+ return pathCandidatesWithOptions(allocator, io, word, replace_start, replace_end, .{}, env);
23012306 }
23022307
23032308 const PathCandidateOptions = struct {
......@@ -2311,12 +2316,14 @@ fn pathCandidatesWithOptions(
23112316 replace_start: usize,
23122317 replace_end: usize,
23132318 options: PathCandidateOptions,
2319+ env: expand.EnvLookup,
23142320 ) ![]Candidate {
23152321 const split = std.mem.findScalarLast(u8, word, '/');
23162322 const dir_prefix = if (split) |index| word[0 .. index + 1] else "";
23172323 const entry_prefix = if (split) |index| word[index + 1 ..] else word;
2318- const dir_path = pathDirectoryToOpen(dir_prefix);
2319-
2324+ const dir_path_raw = if (dir_prefix.len == 0) "." else dir_prefix;
2325+ const dir_path = try expand.expandTilde(allocator, dir_path_raw, env);
2326+ defer allocator.free(dir_path);
23202327 var dir = std.Io.Dir.cwd().openDir(io, dir_path, .{ .iterate = true }) catch |err| switch (err) {
23212328 error.FileNotFound, error.NotDir, error.AccessDenied => return &.{},
23222329 else => return err,
......@@ -2644,8 +2651,8 @@ fn appendShellEscapedValue(
26442651 }
26452652
26462653 fn needsUnquotedEscape(byte: u8, index: usize) bool {
2654+ _ = index;
26472655 if (std.ascii.isWhitespace(byte)) return true;
2648- if (index == 0 and byte == '~') return true;
26492656 return switch (byte) {
26502657 '\\', '\'', '"', '`', '$', '&', '|', ';', '<', '>', '(', ')', '[', ']', '{', '}', '*', '?', '!', '#' => true,
26512658 else => false,
+0 -2 src/editor/driver.zig #
......@@ -32,7 +32,6 @@ const completion_progress_start = "\x1b]9;4;3\x07";
3232 const completion_progress_stop = "\x1b]9;4;0\x07";
3333 const completion_progress_delay_ms = 500;
3434
35-
3635 pub const TerminalEvent = terminal.Event;
3736 pub const ColorScheme = terminal.ColorScheme;
3837 pub const ColorReport = terminal.ColorReport;
......@@ -144,7 +143,6 @@ pub const ReadLineResult = union(enum) {
144143
145144 const completion_flash_ms = 80;
146145
147-
148146 pub const TerminalSession = struct {
149147 allocator: std.mem.Allocator,
150148 io: std.Io,
+0 -1 src/editor/vi.zig #
......@@ -678,4 +678,3 @@ pub fn isAsciiWhitespace(byte: u8) bool {
678678 else => false,
679679 };
680680 }
681-
+7 -1 src/extensions/editor/rush_complete.zig #
......@@ -6,6 +6,7 @@ const api = @import("../api.zig");
66 const editor_completion = @import("../../editor/completion.zig");
77 const shell_builtin = @import("../../shell/builtin.zig");
88 const shell_state_mod = @import("../../shell/state.zig");
9+const expand = @import("../../shell/expand.zig");
910
1011 pub const builtins = [_]shell_builtin.Builtin{
1112 shell_builtin.Builtin.initExtension("rush_complete", .extension_state),
......@@ -36,6 +37,7 @@ pub const State = struct {
3637 operands: []const ParsedOperand,
3738 candidates: std.ArrayList(editor_completion.Candidate) = .empty,
3839 next_source_order: usize = 0,
40+ env_lookup: expand.EnvLookup,
3941
4042 pub fn init(
4143 allocator: std.mem.Allocator,
......@@ -48,6 +50,7 @@ pub const State = struct {
4850 value_position: []const u8,
4951 parsed_options: []const ParsedOption,
5052 operands: []const ParsedOperand,
53+ env_lookup: expand.EnvLookup,
5154 ) State {
5255 return .{
5356 .allocator = allocator,
......@@ -60,6 +63,7 @@ pub const State = struct {
6063 .value_position = value_position,
6164 .parsed_options = parsed_options,
6265 .operands = operands,
66+ .env_lookup = env_lookup,
6367 };
6468 }
6569
......@@ -317,7 +321,9 @@ fn appendPathCandidates(state: *State, directories_only: bool) !void {
317321 const split_after_separator = separator_index != 0 or std.mem.startsWith(u8, state.prefix, "/");
318322 const dir_prefix = if (split_after_separator) state.prefix[0 .. separator_index + 1] else "";
319323 const entry_prefix = if (split_after_separator) state.prefix[separator_index + 1 ..] else state.prefix;
320- const dir_path = if (dir_prefix.len == 0) "." else dir_prefix;
324+ const dir_path_raw = if (dir_prefix.len == 0) "." else dir_prefix;
325+ const dir_path = try expand.expandTilde(state.allocator, dir_path_raw, state.env_lookup);
326+ defer state.allocator.free(dir_path);
321327 var dir = std.Io.Dir.cwd().openDir(state.io, dir_path, .{ .iterate = true }) catch |err| switch (err) {
322328 error.FileNotFound, error.NotDir, error.AccessDenied => return,
323329 else => return err,
+32 -36 src/shell/eval.zig #
......@@ -328,22 +328,19 @@ fn semanticCommandExecutionFrame(
328328 };
329329 try fd_table.applyRedirectionPlan(allocator, redirections);
330330 const stdin: execution_frame.InputEndpoint = switch (fd_table.boundEndpoint(0) orelse
331- @as(execution_frame.FdEndpoint, .{ .input = parent_frame.spec.stdin }))
332- {
331+ @as(execution_frame.FdEndpoint, .{ .input = parent_frame.spec.stdin })) {
333332 .input => |input| input,
334333 .closed => .closed,
335334 .output => parent_frame.spec.stdin,
336335 };
337336 const stdout: execution_frame.OutputEndpoint = switch (fd_table.boundEndpoint(1) orelse
338- @as(execution_frame.FdEndpoint, .{ .output = parent_frame.spec.stdout }))
339- {
337+ @as(execution_frame.FdEndpoint, .{ .output = parent_frame.spec.stdout })) {
340338 .output => |output| output,
341339 .closed => .discard,
342340 .input => parent_frame.spec.stdout,
343341 };
344342 const stderr: execution_frame.OutputEndpoint = switch (fd_table.boundEndpoint(2) orelse
345- @as(execution_frame.FdEndpoint, .{ .output = parent_frame.spec.stderr }))
346- {
343+ @as(execution_frame.FdEndpoint, .{ .output = parent_frame.spec.stderr })) {
347344 .output => |output| output,
348345 .closed => .discard,
349346 .input => parent_frame.spec.stderr,
......@@ -488,8 +485,7 @@ fn captureSet(
488485 .command_substitution_stdout => .{ .channels = &.{ .pipeline_data, .command_substitution_stdout } },
489486 },
490487 .command_substitution_stdout => switch (second orelse
491- return .{ .channels = &.{.command_substitution_stdout} })
492- {
488+ return .{ .channels = &.{.command_substitution_stdout} }) {
493489 .side_stdout => .{ .channels = &.{ .command_substitution_stdout, .side_stdout } },
494490 .side_stderr => .{ .channels = &.{ .command_substitution_stdout, .side_stderr } },
495491 .pipeline_data => .{ .channels = &.{ .command_substitution_stdout, .pipeline_data } },
......@@ -5172,24 +5168,24 @@ fn evaluateCommandSubstitutionBody(
51725168 .simple => |plan| blk: {
51735169 var input = EvaluationInput.empty();
51745170 break :blk evaluatePlanWithInput(
5175- evaluator,
5176- substitution_state,
5177- substitution_context.withTarget(plan.target),
5178- plan,
5179- &input,
5180- frame,
5181- );
5171+ evaluator,
5172+ substitution_state,
5173+ substitution_context.withTarget(plan.target),
5174+ plan,
5175+ &input,
5176+ frame,
5177+ );
51825178 },
51835179 .compound => |plan| blk: {
51845180 var input = EvaluationInput.empty();
51855181 break :blk evaluateCompoundPlanWithInput(
5186- evaluator,
5187- substitution_state,
5188- substitution_context.withTarget(plan.target),
5189- plan,
5190- &input,
5191- frame,
5192- );
5182+ evaluator,
5183+ substitution_state,
5184+ substitution_context.withTarget(plan.target),
5185+ plan,
5186+ &input,
5187+ frame,
5188+ );
51935189 },
51945190 .pipeline => |plan| evaluatePipelinePlanWithFrame(
51955191 evaluator,
......@@ -5230,24 +5226,24 @@ fn evaluateCommandSubstitutionBodyPayload(
52305226 .simple => |plan| blk: {
52315227 var input = EvaluationInput.empty();
52325228 break :blk evaluatePlanWithInput(
5233- evaluator,
5234- substitution_state,
5235- substitution_context.withTarget(plan.target),
5236- plan,
5237- &input,
5238- frame,
5239- );
5229+ evaluator,
5230+ substitution_state,
5231+ substitution_context.withTarget(plan.target),
5232+ plan,
5233+ &input,
5234+ frame,
5235+ );
52405236 },
52415237 .compound => |plan| blk: {
52425238 var input = EvaluationInput.empty();
52435239 break :blk evaluateCompoundPlanWithInput(
5244- evaluator,
5245- substitution_state,
5246- substitution_context.withTarget(plan.target),
5247- plan,
5248- &input,
5249- frame,
5250- );
5240+ evaluator,
5241+ substitution_state,
5242+ substitution_context.withTarget(plan.target),
5243+ plan,
5244+ &input,
5245+ frame,
5246+ );
52515247 },
52525248 .pipeline => |plan| evaluatePipelinePlanWithFrame(
52535249 evaluator,
+14 -1 src/shell/state.zig #
......@@ -8,6 +8,7 @@ const command_plan = @import("command_plan.zig");
88 const context = @import("context.zig");
99 const runtime_process = @import("../runtime/process.zig");
1010 const trap_semantics = @import("trap.zig");
11+const expand = @import("expand.zig");
1112
1213 pub const ExitStatus = u8;
1314 pub const TrapSignal = trap_semantics.Signal;
......@@ -221,7 +222,6 @@ pub const VariableAttributes = struct {
221222 readonly: bool = false,
222223 };
223224
224-
225225 pub const Variable = struct {
226226 value: []const u8,
227227 exported: bool = false,
......@@ -430,6 +430,12 @@ pub const BackgroundJobNotification = struct {
430430 }
431431 };
432432
433+fn shellStateLookup(ctx: ?*const anyopaque, name: []const u8) ?[]const u8 {
434+ const state: *const ShellState = @ptrCast(@alignCast(ctx.?));
435+ const vr = state.getVariable(name);
436+ return if (vr) |v| v.value else null;
437+}
438+
433439 pub const ShellState = struct {
434440 allocator: std.mem.Allocator,
435441 scope: Scope = .current_shell,
......@@ -460,6 +466,13 @@ pub const ShellState = struct {
460466 running,
461467 };
462468
469+ pub fn envLookup(self: *const ShellState) expand.EnvLookup {
470+ return .{
471+ .context = self,
472+ .lookupFn = shellStateLookup,
473+ };
474+ }
475+
463476 pub fn init(allocator: std.mem.Allocator) ShellState {
464477 return .{ .allocator = allocator };
465478 }
Back to top