summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 16:45:54 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commitc9d5d97e4d487b92a52d765dd9c467423ddd1de1 (patch)
tree35547658ee733fb2631a41568c682c93b5fd4746
parentd09c1c36e4d4e1bc57aa14f5ba4c6131b6111429 (diff)
downloadpardes-c9d5d97e4d487b92a52d765dd9c467423ddd1de1.tar.gz
pardes-c9d5d97e4d487b92a52d765dd9c467423ddd1de1.zip
A REPL's language is named and found as the syntax table finds one: aliases, any case
Repl took only the grammar's exact name, and a file's language was found by a case-sensitive suffix of its path, apart from the aliases a code fence already had. The aliases move to grammar_manifest with byName and byPath, which syntax, Repl and langOf share, so `Repl py` and a .PY file work. The line-by-line warning is for the python language, not an id that starts with python. Joincol's doc line was sitting on Repl. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/builtins.zig11
-rw-r--r--src/exec.zig11
-rw-r--r--src/grammar_manifest.zig43
-rw-r--r--src/pardes.zig3
-rw-r--r--src/syntax.zig29
5 files changed, 58 insertions, 39 deletions
diff --git a/src/builtins.zig b/src/builtins.zig
index ed42dfe3..79c45325 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -903,7 +903,6 @@ pub const Tty9p = struct {
};
/// Fold the active pane's column into the one on its right, keeping its panes.
-/// The horizontal mirror of the vertical stacking `New` does.
/// Binds this terminal as a language's REPL, by the language's name as the
/// syntax table has it: an exec made on the body of a file in that
/// language -- a middle click, the execute key, a single word like `ls`
@@ -934,10 +933,11 @@ pub const Repl = struct {
pane.repl = null;
return c.p.setMessage(c.id, "Repl: unbound");
}
- const grammars = @import("grammar_manifest.zig").all;
- const lang: u8 = for (grammars, 0..) |g, i| {
- if (g.exts.len > 0 and std.mem.eql(u8, g.name, arg)) break @intCast(i);
- } else {
+ const manifest = @import("grammar_manifest.zig");
+ const grammars = manifest.all;
+ // Named as a code fence names it: `py`, `Python`, `sh`.
+ const named: ?u8 = if (manifest.byName(arg)) |i| if (grammars[i].exts.len > 0) @intCast(i) else null else null;
+ const lang: u8 = named orelse {
var w = std.Io.Writer.fixed(&buf);
w.print("Repl: no language \"{s}\"; one of", .{arg[0..@min(arg.len, 32)]}) catch {};
for (grammars) |g| if (g.exts.len > 0) w.print(" {s}", .{g.name}) catch break;
@@ -962,6 +962,7 @@ pub const Repl = struct {
}
};
+/// The horizontal mirror of the vertical stacking `New` does.
pub const Joincol = struct {
pub const scope: Scope = .session;
pub fn run(c: Ctx) void {
diff --git a/src/exec.zig b/src/exec.zig
index 9a4e0a89..12934c3c 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -17,6 +17,10 @@ const themes = colors.themes;
const builtins = @import("builtins.zig");
const config = @import("config.zig");
const grammars = @import("grammar_manifest.zig").all;
+const python_lang = blk: {
+ @setEvalBranchQuota(20_000);
+ break :blk @import("grammar_manifest.zig").byName("python").?;
+};
const pdf_enabled = panes.Pdf.enabled;
const font_picker = pardes.font_picker;
const hosted = pardes.hosted;
@@ -387,10 +391,7 @@ pub fn replaceStillborn(p: *Pardes) void {
/// A file pane's language, as grammar_manifest names it, by its index.
pub fn langOf(pane: *const Pane) ?u8 {
const f = pane.file orelse return null;
- for (grammars, 0..) |g, i| for (g.exts) |ext| {
- if (std.mem.endsWith(u8, f.path, ext)) return @intCast(i);
- };
- return null;
+ return @intCast(@import("grammar_manifest.zig").byPath(f.path) orelse return null);
}
/// A bound REPL whose program is there to take input: a command pane's
@@ -490,7 +491,7 @@ pub fn sendToRepl(p: *Pardes, from: usize, to: usize, text: []const u8) void {
edit.typeToTty(p, to, dst, clean[0..n]);
p.emitWrite(to, "\r");
// Line by line, a blank line inside a Python block ends the block.
- if (!panes.Terminal.bracketedPaste(dst) and !dst.repl_warned and std.mem.startsWith(u8, id, "python") and
+ if (!panes.Terminal.bracketedPaste(dst) and !dst.repl_warned and (if (dst.repl) |r| r.lang == python_lang else false) and
std.mem.indexOfScalar(u8, text, '\n') != null)
{
dst.repl_warned = true;
diff --git a/src/grammar_manifest.zig b/src/grammar_manifest.zig
index 0303af17..d6666729 100644
--- a/src/grammar_manifest.zig
+++ b/src/grammar_manifest.zig
@@ -2,6 +2,8 @@
//! runtime syntax registry. Keep target symbols and query values out of this
//! file: the build runner imports it as host code.
+const std = @import("std");
+
pub const Tier = enum { zig, minimal, full };
pub const Grammar = struct {
@@ -49,3 +51,44 @@ pub const all = [_]Grammar{
.{ .name = "typst", .dep = "ts_typst", .exts = &.{ ".typ", ".typst" }, .tier = .full, .scanner = true, .query = "queries/typst/highlights.scm" },
.{ .name = "zig", .dep = "ts_zig", .exts = &.{ ".zig", ".zon" }, .tier = .zig },
};
+
+/// The names a language goes by besides its grammar's: a fence's `py`,
+/// a `Repl sh`.
+pub const aliases = [_]struct { []const u8, []const u8 }{
+ .{ "js", "javascript" },
+ .{ "jsx", "javascript" },
+ .{ "mjs", "javascript" },
+ .{ "py", "python" },
+ .{ "sh", "bash" },
+ .{ "shell", "bash" },
+ .{ "zsh", "bash" },
+ .{ "bash", "bash" },
+ .{ "rs", "rust" },
+ .{ "c++", "cpp" },
+ .{ "cxx", "cpp" },
+ .{ "cc", "cpp" },
+ .{ "cs", "c_sharp" },
+ .{ "kt", "kotlin" },
+ .{ "rb", "ruby" },
+ .{ "ml", "ocaml" },
+ .{ "hs", "haskell" },
+};
+
+/// The grammar a language name means: its own name or an alias, in any case.
+pub fn byName(name: []const u8) ?usize {
+ var canonical = name;
+ for (aliases) |a| if (std.ascii.eqlIgnoreCase(name, a[0])) {
+ canonical = a[1];
+ break;
+ };
+ for (all, 0..) |g, i| if (std.ascii.eqlIgnoreCase(canonical, g.name)) return i;
+ return null;
+}
+
+/// The grammar a path's extension selects, in any case.
+pub fn byPath(path: []const u8) ?usize {
+ const ext = std.fs.path.extension(path);
+ if (ext.len == 0) return null;
+ for (all, 0..) |g, i| for (g.exts) |e| if (std.ascii.eqlIgnoreCase(ext, e)) return i;
+ return null;
+}
diff --git a/src/pardes.zig b/src/pardes.zig
index 20406895..c5413672 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -1458,7 +1458,8 @@ test "a REPL bound for a language takes the exec made on a body in it; tags, Exe
try std.testing.expect(p.panes[1].?.repl == null);
try std.testing.expect(std.mem.indexOf(u8, p.panes[1].?.msg[0..p.panes[1].?.msg_len], "python") != null);
try std.testing.expect(p.executeBuiltinLine(1, "Repl python"));
- try std.testing.expect(p.executeBuiltinLine(1, "Repl python")); // again: no change
+ // again, as a code fence may name it: no change
+ try std.testing.expect(p.executeBuiltinLine(1, "Repl PY"));
var idbuf: [48]u8 = undefined;
try std.testing.expectEqualStrings("python-a", exec.replId(&idbuf, p.panes[1].?));
try std.testing.expect(std.mem.endsWith(u8, try tagline.tagPrefix(p, p.panes[1].?), " python-a"));
diff --git a/src/syntax.zig b/src/syntax.zig
index 9c1a4f4b..9930db20 100644
--- a/src/syntax.zig
+++ b/src/syntax.zig
@@ -226,35 +226,8 @@ fn forExt(ext: []const u8) !?Selected {
return null;
}
-const LangAlias = struct { []const u8, []const u8 };
-const lang_aliases = [_]LangAlias{
- .{ "js", "javascript" },
- .{ "jsx", "javascript" },
- .{ "mjs", "javascript" },
- .{ "py", "python" },
- .{ "sh", "bash" },
- .{ "shell", "bash" },
- .{ "zsh", "bash" },
- .{ "bash", "bash" },
- .{ "rs", "rust" },
- .{ "c++", "cpp" },
- .{ "cxx", "cpp" },
- .{ "cc", "cpp" },
- .{ "cs", "c_sharp" },
- .{ "kt", "kotlin" },
- .{ "rb", "ruby" },
- .{ "ml", "ocaml" },
- .{ "hs", "haskell" },
-};
-
fn forLang(name: []const u8) !?Selected {
- var canonical = name;
- for (lang_aliases) |a| {
- if (std.ascii.eqlIgnoreCase(name, a[0])) {
- canonical = a[1];
- break;
- }
- }
+ const canonical = if (grammar_manifest.byName(name)) |i| grammar_manifest.all[i].name else name;
for (&specs) |*spec| {
if (std.ascii.eqlIgnoreCase(canonical, spec.name)) return try ensure(spec);
}