move away from deprecated thingies/bump srf
All checks were successful
Generic zig build / build (push) Successful in 22s
All checks were successful
Generic zig build / build (push) Successful in 22s
This commit is contained in:
parent
da49d843ca
commit
eb6cc1f388
7 changed files with 128 additions and 80 deletions
|
|
@ -5,8 +5,8 @@
|
||||||
.minimum_zig_version = "0.16.0",
|
.minimum_zig_version = "0.16.0",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.srf = .{
|
.srf = .{
|
||||||
.url = "git+https://git.lerch.org/lobo/srf.git#7f3a43e838fc472bdb1af6c1b692f2255dd19f48",
|
.url = "git+https://git.lerch.org/lobo/srf.git#af8b998d3fd3f240277d7abf19343d3ddc25ed3a",
|
||||||
.hash = "srf-0.0.0-qZj57-17AgAXn2at0WH1xV75oJhr3vTFeU5FxU7kAhtf",
|
.hash = "srf-0.0.0-qZj57wqMAgAUtX8nN3ZyshqlUGDNjsyZqCUcguVqI2Og",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||||
zigDeps = zig.fetchDeps {
|
zigDeps = zig.fetchDeps {
|
||||||
inherit (finalAttrs) pname version src;
|
inherit (finalAttrs) pname version src;
|
||||||
fetchAll = true;
|
fetchAll = true;
|
||||||
hash = "sha256-0A2IlywPDkgX8+oiZ1Xd2zF0o07T4moBRngddwl1GBk=";
|
hash = "sha256-yWUmqKxop3uCqvHBrb16pgAgA2O+0kholkY+M7Zftbk=";
|
||||||
};
|
};
|
||||||
|
|
||||||
postConfigure = ''
|
postConfigure = ''
|
||||||
|
|
|
||||||
|
|
@ -430,14 +430,14 @@ const Harness = struct {
|
||||||
/// Asserts the transcript contains `needle`, which keeps the tests readable
|
/// Asserts the transcript contains `needle`, which keeps the tests readable
|
||||||
/// without reimplementing a JSON-RPC client here.
|
/// without reimplementing a JSON-RPC client here.
|
||||||
fn expectSent(self: *Harness, needle: []const u8) !void {
|
fn expectSent(self: *Harness, needle: []const u8) !void {
|
||||||
if (std.mem.indexOf(u8, self.transcript(), needle) == null) {
|
if (std.mem.find(u8, self.transcript(), needle) == null) {
|
||||||
std.debug.print("\nexpected to find: {s}\nin transcript:\n{s}\n", .{ needle, self.transcript() });
|
std.debug.print("\nexpected to find: {s}\nin transcript:\n{s}\n", .{ needle, self.transcript() });
|
||||||
return error.NotSent;
|
return error.NotSent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expectNotSent(self: *Harness, needle: []const u8) !void {
|
fn expectNotSent(self: *Harness, needle: []const u8) !void {
|
||||||
if (std.mem.indexOf(u8, self.transcript(), needle) != null) {
|
if (std.mem.find(u8, self.transcript(), needle) != null) {
|
||||||
std.debug.print("\nexpected NOT to find: {s}\nin transcript:\n{s}\n", .{ needle, self.transcript() });
|
std.debug.print("\nexpected NOT to find: {s}\nin transcript:\n{s}\n", .{ needle, self.transcript() });
|
||||||
return error.UnexpectedlySent;
|
return error.UnexpectedlySent;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -442,14 +442,14 @@ test "missing magic header is reported on line 0" {
|
||||||
try testing.expect(diags.len > 0);
|
try testing.expect(diags.len > 0);
|
||||||
try testing.expectEqual(@as(u32, 0), diags[0].range.start.line);
|
try testing.expectEqual(@as(u32, 0), diags[0].range.start.line);
|
||||||
try testing.expectEqual(Severity.err, diags[0].severity);
|
try testing.expectEqual(Severity.err, diags[0].severity);
|
||||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "Magic header") != null);
|
try testing.expect(std.mem.find(u8, diags[0].message, "Magic header") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "duplicate magic header is reported" {
|
test "duplicate magic header is reported" {
|
||||||
const diags = try analyzeForTest("#!srfv1\n#!srfv1\nname::alice\n");
|
const diags = try analyzeForTest("#!srfv1\n#!srfv1\nname::alice\n");
|
||||||
defer freeDiagnostics(testing.allocator, diags);
|
defer freeDiagnostics(testing.allocator, diags);
|
||||||
try testing.expect(diags.len > 0);
|
try testing.expect(diags.len > 0);
|
||||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "duplicate magic") != null);
|
try testing.expect(std.mem.find(u8, diags[0].message, "duplicate magic") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "empty document reports the missing header" {
|
test "empty document reports the missing header" {
|
||||||
|
|
@ -458,8 +458,8 @@ test "empty document reports the missing header" {
|
||||||
const diags = try analyzeForTest("");
|
const diags = try analyzeForTest("");
|
||||||
defer freeDiagnostics(testing.allocator, diags);
|
defer freeDiagnostics(testing.allocator, diags);
|
||||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "empty") != null);
|
try testing.expect(std.mem.find(u8, diags[0].message, "empty") != null);
|
||||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "#!srfv1") != null);
|
try testing.expect(std.mem.find(u8, diags[0].message, "#!srfv1") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "whitespace-only document is treated as empty" {
|
test "whitespace-only document is treated as empty" {
|
||||||
|
|
@ -569,8 +569,8 @@ test "a long-format overrun is left to srf, which reports it as of 4bec474" {
|
||||||
const all = try analyzeForTest("#!srfv1\n#!long\nk:3:hello\n");
|
const all = try analyzeForTest("#!srfv1\n#!long\nk:3:hello\n");
|
||||||
defer freeDiagnostics(testing.allocator, all);
|
defer freeDiagnostics(testing.allocator, all);
|
||||||
try testing.expectEqual(@as(usize, 1), all.len);
|
try testing.expectEqual(@as(usize, 1), all.len);
|
||||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "additional bytes after field value") != null);
|
try testing.expect(std.mem.find(u8, all[0].message, "additional bytes after field value") != null);
|
||||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "restated as 5") != null);
|
try testing.expect(std.mem.find(u8, all[0].message, "restated as 5") != null);
|
||||||
try testing.expectEqual(@as(u32, 2), all[0].range.start.line);
|
try testing.expectEqual(@as(u32, 2), all[0].range.start.line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -579,7 +579,7 @@ test "a length prefix past the end of the file is an error" {
|
||||||
defer freeDiagnostics(testing.allocator, diags);
|
defer freeDiagnostics(testing.allocator, diags);
|
||||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||||
try testing.expectEqual(Severity.err, diags[0].severity);
|
try testing.expectEqual(Severity.err, diags[0].severity);
|
||||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "declares 99 bytes but only 6 remain") != null);
|
try testing.expect(std.mem.find(u8, diags[0].message, "declares 99 bytes but only 6 remain") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "a compact-format overrun is left to srf, which reports it" {
|
test "a compact-format overrun is left to srf, which reports it" {
|
||||||
|
|
@ -593,8 +593,8 @@ test "a compact-format overrun is left to srf, which reports it" {
|
||||||
try testing.expectEqual(@as(usize, 1), all.len);
|
try testing.expectEqual(@as(usize, 1), all.len);
|
||||||
try testing.expectEqual(Severity.err, all[0].severity);
|
try testing.expectEqual(Severity.err, all[0].severity);
|
||||||
try testing.expectEqual(@as(u32, 1), all[0].range.start.line);
|
try testing.expectEqual(@as(u32, 1), all[0].range.start.line);
|
||||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "2 additional bytes") != null);
|
try testing.expect(std.mem.find(u8, all[0].message, "2 additional bytes") != null);
|
||||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "restated as 5") != null);
|
try testing.expect(std.mem.find(u8, all[0].message, "restated as 5") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "a compact overrun with no following delimiter still names the real problem" {
|
test "a compact overrun with no following delimiter still names the real problem" {
|
||||||
|
|
@ -605,8 +605,8 @@ test "a compact overrun with no following delimiter still names the real problem
|
||||||
const all = try analyzeForTest("#!srfv1\nk:2:abc\n");
|
const all = try analyzeForTest("#!srfv1\nk:2:abc\n");
|
||||||
defer freeDiagnostics(testing.allocator, all);
|
defer freeDiagnostics(testing.allocator, all);
|
||||||
try testing.expect(all.len >= 1);
|
try testing.expect(all.len >= 1);
|
||||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "1 additional bytes") != null);
|
try testing.expect(std.mem.find(u8, all[0].message, "1 additional bytes") != null);
|
||||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "restated as 3") != null);
|
try testing.expect(std.mem.find(u8, all[0].message, "restated as 3") != null);
|
||||||
}
|
}
|
||||||
test "a correct length prefix produces no diagnostic" {
|
test "a correct length prefix produces no diagnostic" {
|
||||||
const diags = try analyzeForTest("#!srfv1\n#!long\nk:5:hello\n");
|
const diags = try analyzeForTest("#!srfv1\n#!long\nk:5:hello\n");
|
||||||
|
|
@ -640,8 +640,8 @@ test "length diagnostics coexist with srf's own diagnostics" {
|
||||||
var from_srf = false;
|
var from_srf = false;
|
||||||
var from_us = false;
|
var from_us = false;
|
||||||
for (diags) |d| {
|
for (diags) |d| {
|
||||||
if (std.mem.indexOf(u8, d.message, "duplicate magic") != null) from_srf = true;
|
if (std.mem.find(u8, d.message, "duplicate magic") != null) from_srf = true;
|
||||||
if (std.mem.indexOf(u8, d.message, "length prefix") != null) from_us = true;
|
if (std.mem.find(u8, d.message, "length prefix") != null) from_us = true;
|
||||||
}
|
}
|
||||||
try testing.expect(from_srf);
|
try testing.expect(from_srf);
|
||||||
try testing.expect(from_us);
|
try testing.expect(from_us);
|
||||||
|
|
@ -653,7 +653,7 @@ test "a compact overrun does not produce the unlocated fallback" {
|
||||||
const diags = try analyzeForTest("#!srfv1\nk:2:abc\n");
|
const diags = try analyzeForTest("#!srfv1\nk:2:abc\n");
|
||||||
defer freeDiagnostics(testing.allocator, diags);
|
defer freeDiagnostics(testing.allocator, diags);
|
||||||
for (diags) |d| {
|
for (diags) |d| {
|
||||||
try testing.expect(std.mem.indexOf(u8, d.message, "reported no location") == null);
|
try testing.expect(std.mem.find(u8, d.message, "reported no location") == null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -669,20 +669,20 @@ test "a located length problem replaces the unlocated fallback" {
|
||||||
const diags = try analyzeForTest("#!srfv1\n#!long\nk:99:hello\n");
|
const diags = try analyzeForTest("#!srfv1\n#!long\nk:99:hello\n");
|
||||||
defer freeDiagnostics(testing.allocator, diags);
|
defer freeDiagnostics(testing.allocator, diags);
|
||||||
for (diags) |d| {
|
for (diags) |d| {
|
||||||
if (std.mem.indexOf(u8, d.message, "reported no location") != null) {
|
if (std.mem.find(u8, d.message, "reported no location") != null) {
|
||||||
std.debug.print("\nunlocated fallback should have been suppressed\n", .{});
|
std.debug.print("\nunlocated fallback should have been suppressed\n", .{});
|
||||||
return error.UnexpectedFallback;
|
return error.UnexpectedFallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "declares 99 bytes but only 6 remain") != null);
|
try testing.expect(std.mem.find(u8, diags[0].message, "declares 99 bytes but only 6 remain") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "the unlocated fallback still fires when nothing else explains the failure" {
|
test "the unlocated fallback still fires when nothing else explains the failure" {
|
||||||
const diags = try analyzeForTest("");
|
const diags = try analyzeForTest("");
|
||||||
defer freeDiagnostics(testing.allocator, diags);
|
defer freeDiagnostics(testing.allocator, diags);
|
||||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "empty") != null);
|
try testing.expect(std.mem.find(u8, diags[0].message, "empty") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Only the type-consistency warnings, without srf's own diagnostics.
|
/// Only the type-consistency warnings, without srf's own diagnostics.
|
||||||
|
|
@ -804,7 +804,7 @@ test "a one against one tie flags the later occurrence" {
|
||||||
defer freeDiagnostics(testing.allocator, diags);
|
defer freeDiagnostics(testing.allocator, diags);
|
||||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||||
try testing.expectEqual(@as(u32, 4), diags[0].range.start.line);
|
try testing.expectEqual(@as(u32, 4), diags[0].range.start.line);
|
||||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "is `num` in 1 other") != null);
|
try testing.expect(std.mem.find(u8, diags[0].message, "is `num` in 1 other") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "the three string spellings are one kind, not three" {
|
test "the three string spellings are one kind, not three" {
|
||||||
|
|
@ -825,12 +825,12 @@ test "bool and binary inconsistencies are reported too" {
|
||||||
const bools = try inconsistenciesForTest("#!srfv1\n#!long\nk:bool:true\n\nk:bool:false\n\nk::true\n");
|
const bools = try inconsistenciesForTest("#!srfv1\n#!long\nk:bool:true\n\nk:bool:false\n\nk::true\n");
|
||||||
defer freeDiagnostics(testing.allocator, bools);
|
defer freeDiagnostics(testing.allocator, bools);
|
||||||
try testing.expectEqual(@as(usize, 1), bools.len);
|
try testing.expectEqual(@as(usize, 1), bools.len);
|
||||||
try testing.expect(std.mem.indexOf(u8, bools[0].message, "`bool`") != null);
|
try testing.expect(std.mem.find(u8, bools[0].message, "`bool`") != null);
|
||||||
|
|
||||||
const bins = try inconsistenciesForTest("#!srfv1\n#!long\nk:binary:aGk=\n\nk:binary:aGk=\n\nk::aGk=\n");
|
const bins = try inconsistenciesForTest("#!srfv1\n#!long\nk:binary:aGk=\n\nk:binary:aGk=\n\nk::aGk=\n");
|
||||||
defer freeDiagnostics(testing.allocator, bins);
|
defer freeDiagnostics(testing.allocator, bins);
|
||||||
try testing.expectEqual(@as(usize, 1), bins.len);
|
try testing.expectEqual(@as(usize, 1), bins.len);
|
||||||
try testing.expect(std.mem.indexOf(u8, bins[0].message, "`binary`") != null);
|
try testing.expect(std.mem.find(u8, bins[0].message, "`binary`") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "keys are compared byte exactly, matching srf" {
|
test "keys are compared byte exactly, matching srf" {
|
||||||
|
|
@ -853,8 +853,8 @@ test "type warnings coexist with length warnings without duplication" {
|
||||||
var type_warnings: usize = 0;
|
var type_warnings: usize = 0;
|
||||||
var length_warnings: usize = 0;
|
var length_warnings: usize = 0;
|
||||||
for (diags) |d| {
|
for (diags) |d| {
|
||||||
if (std.mem.indexOf(u8, d.message, "other field(s) in this file") != null) type_warnings += 1;
|
if (std.mem.find(u8, d.message, "other field(s) in this file") != null) type_warnings += 1;
|
||||||
if (std.mem.indexOf(u8, d.message, "length prefix") != null) length_warnings += 1;
|
if (std.mem.find(u8, d.message, "length prefix") != null) length_warnings += 1;
|
||||||
}
|
}
|
||||||
try testing.expectEqual(@as(usize, 1), type_warnings);
|
try testing.expectEqual(@as(usize, 1), type_warnings);
|
||||||
try testing.expectEqual(@as(usize, 1), length_warnings);
|
try testing.expectEqual(@as(usize, 1), length_warnings);
|
||||||
|
|
@ -910,7 +910,7 @@ test "compact format does not allow trailing whitespace after a length prefix" {
|
||||||
const all = try analyzeForTest("#!srfv1\nk:5:hello \n");
|
const all = try analyzeForTest("#!srfv1\nk:5:hello \n");
|
||||||
defer freeDiagnostics(testing.allocator, all);
|
defer freeDiagnostics(testing.allocator, all);
|
||||||
try testing.expect(all.len > 0);
|
try testing.expect(all.len > 0);
|
||||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "additional bytes") != null);
|
try testing.expect(std.mem.find(u8, all[0].message, "additional bytes") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "a bad length prefix followed by more lines does not crash the parser" {
|
test "a bad length prefix followed by more lines does not crash the parser" {
|
||||||
|
|
@ -947,7 +947,7 @@ test "a recovered short prefix reports once when the rest of the line is valid"
|
||||||
defer freeDiagnostics(testing.allocator, diags);
|
defer freeDiagnostics(testing.allocator, diags);
|
||||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||||
try testing.expectEqual(@as(u32, 1), diags[0].range.start.line);
|
try testing.expectEqual(@as(u32, 1), diags[0].range.start.line);
|
||||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "restated as 5") != null);
|
try testing.expect(std.mem.find(u8, diags[0].message, "restated as 5") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "each bad length prefix in a document is reported once, on its own line" {
|
test "each bad length prefix in a document is reported once, on its own line" {
|
||||||
|
|
@ -956,7 +956,7 @@ test "each bad length prefix in a document is reported once, on its own line" {
|
||||||
try testing.expectEqual(@as(usize, 3), diags.len);
|
try testing.expectEqual(@as(usize, 3), diags.len);
|
||||||
for (diags, 1..) |d, line| {
|
for (diags, 1..) |d, line| {
|
||||||
try testing.expectEqual(@as(u32, @intCast(line)), d.range.start.line);
|
try testing.expectEqual(@as(u32, @intCast(line)), d.range.start.line);
|
||||||
try testing.expect(std.mem.indexOf(u8, d.message, "additional bytes") != null);
|
try testing.expect(std.mem.find(u8, d.message, "additional bytes") != null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ const Scanner = struct {
|
||||||
while (end < line_end and !isSpace(self.text[end])) end += 1;
|
while (end < line_end and !isSpace(self.text[end])) end += 1;
|
||||||
|
|
||||||
const decl = self.text[body + 2 .. end];
|
const decl = self.text[body + 2 .. end];
|
||||||
const eq = std.mem.indexOfScalar(u8, decl, '=');
|
const eq = std.mem.findScalar(u8, decl, '=');
|
||||||
const name = if (eq) |i| decl[0..i] else decl;
|
const name = if (eq) |i| decl[0..i] else decl;
|
||||||
|
|
||||||
// Mode directives take effect for everything below them.
|
// Mode directives take effect for everything below them.
|
||||||
|
|
@ -283,7 +283,7 @@ const Scanner = struct {
|
||||||
/// another field follows it on the same line.
|
/// another field follows it on the same line.
|
||||||
fn scanField(self: *Scanner, field_start: usize) ?Item {
|
fn scanField(self: *Scanner, field_start: usize) ?Item {
|
||||||
// Key runs to the first ':'.
|
// Key runs to the first ':'.
|
||||||
const colon = std.mem.indexOfScalarPos(u8, self.text[0..self.line_end], field_start, ':') orelse
|
const colon = std.mem.findScalarPos(u8, self.text[0..self.line_end], field_start, ':') orelse
|
||||||
return self.abandonLine();
|
return self.abandonLine();
|
||||||
const head = self.splitHead(colon) orelse return self.abandonLine();
|
const head = self.splitHead(colon) orelse return self.abandonLine();
|
||||||
|
|
||||||
|
|
@ -336,7 +336,7 @@ const Scanner = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// `key:hint:value`, where the hint may be padded with whitespace.
|
// `key:hint:value`, where the hint may be padded with whitespace.
|
||||||
const second = std.mem.indexOfScalarPos(u8, self.text[0..self.line_end], colon + 1, ':') orelse
|
const second = std.mem.findScalarPos(u8, self.text[0..self.line_end], colon + 1, ':') orelse
|
||||||
return null;
|
return null;
|
||||||
var start = colon + 1;
|
var start = colon + 1;
|
||||||
var end = second;
|
var end = second;
|
||||||
|
|
@ -380,7 +380,7 @@ const Scanner = struct {
|
||||||
else if (end < new_line_end and self.text[end] == ',')
|
else if (end < new_line_end and self.text[end] == ',')
|
||||||
end + 1
|
end + 1
|
||||||
else if (problem != null)
|
else if (problem != null)
|
||||||
if (std.mem.indexOfScalarPos(u8, self.text[0..new_line_end], end, ',')) |comma|
|
if (std.mem.findScalarPos(u8, self.text[0..new_line_end], end, ',')) |comma|
|
||||||
comma + 1
|
comma + 1
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
|
|
@ -406,7 +406,7 @@ const Scanner = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compact format: the next comma ends the value and starts a field.
|
// Compact format: the next comma ends the value and starts a field.
|
||||||
if (std.mem.indexOfScalarPos(u8, self.text[0..self.line_end], value_start, ',')) |comma| {
|
if (std.mem.findScalarPos(u8, self.text[0..self.line_end], value_start, ',')) |comma| {
|
||||||
return .{
|
return .{
|
||||||
.end = comma,
|
.end = comma,
|
||||||
.next_field = comma + 1,
|
.next_field = comma + 1,
|
||||||
|
|
@ -455,17 +455,9 @@ const Scanner = struct {
|
||||||
// and a range built from the first line's end would come out inverted.
|
// and a range built from the first line's end would come out inverted.
|
||||||
//
|
//
|
||||||
// How far the surplus runs has to match how srf counts it, or hover ends
|
// How far the surplus runs has to match how srf counts it, or hover ends
|
||||||
// up suggesting a different corrected length than the diagnostic does. srf
|
// up suggesting a different corrected length than the diagnostic does.
|
||||||
// stops at the next delimiter in compact format (`extra_bytes` in
|
|
||||||
// `checkShortPrefix`) and runs to end of line in long format, where a
|
|
||||||
// comma is ordinary text.
|
|
||||||
const line_end = self.lineEnd(after);
|
const line_end = self.lineEnd(after);
|
||||||
const surplus_end = if (self.long_mode)
|
const surplus_end = self.likelySurplusEnd(after, line_end);
|
||||||
line_end
|
|
||||||
else if (std.mem.indexOfScalarPos(u8, self.text[0..line_end], after, ',')) |comma|
|
|
||||||
comma
|
|
||||||
else
|
|
||||||
line_end;
|
|
||||||
|
|
||||||
return .{ .overrun = .{
|
return .{ .overrun = .{
|
||||||
.declared = declared,
|
.declared = declared,
|
||||||
|
|
@ -474,8 +466,30 @@ const Scanner = struct {
|
||||||
} };
|
} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Where the value most likely actually ends, mirroring srf's
|
||||||
|
/// `likelyExtraBytes`.
|
||||||
|
///
|
||||||
|
/// A comma is ambiguous in compact format: field separator, or content inside
|
||||||
|
/// a length-prefixed value, or even part of a key. The byte count normally
|
||||||
|
/// tells them apart, so with the count wrong the next ':' is the best anchor
|
||||||
|
/// left, being where the following field's key ends; the comma just before it
|
||||||
|
/// is therefore the separator. Falling back to the first comma covers a value
|
||||||
|
/// that contains a colon itself, such as a URL.
|
||||||
|
///
|
||||||
|
/// Long format has no comma delimiter, so the whole remainder of the line is
|
||||||
|
/// surplus there.
|
||||||
|
fn likelySurplusEnd(self: *const Scanner, after: usize, line_end: usize) usize {
|
||||||
|
if (self.long_mode) return line_end;
|
||||||
|
|
||||||
|
const remainder = self.text[after..line_end];
|
||||||
|
if (std.mem.findScalar(u8, remainder, ':')) |colon|
|
||||||
|
if (std.mem.findScalarLast(u8, remainder[0..colon], ',')) |separator|
|
||||||
|
return after + separator;
|
||||||
|
return after + (std.mem.findScalar(u8, remainder, ',') orelse remainder.len);
|
||||||
|
}
|
||||||
|
|
||||||
fn lineEnd(self: *const Scanner, from: usize) usize {
|
fn lineEnd(self: *const Scanner, from: usize) usize {
|
||||||
return std.mem.indexOfScalarPos(u8, self.text, from, '\n') orelse self.text.len;
|
return std.mem.findScalarPos(u8, self.text, from, '\n') orelse self.text.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nextLine(self: *const Scanner, line_end: usize) usize {
|
fn nextLine(self: *const Scanner, line_end: usize) usize {
|
||||||
|
|
@ -502,7 +516,7 @@ const testing = std.testing;
|
||||||
/// Finds `needle` in `text` and returns the offset of its midpoint, which is a
|
/// Finds `needle` in `text` and returns the offset of its midpoint, which is a
|
||||||
/// stable way to say "hover here" without hand-counting byte offsets.
|
/// stable way to say "hover here" without hand-counting byte offsets.
|
||||||
fn midpointOf(text: []const u8, needle: []const u8) usize {
|
fn midpointOf(text: []const u8, needle: []const u8) usize {
|
||||||
const at = std.mem.indexOf(u8, text, needle).?;
|
const at = std.mem.find(u8, text, needle).?;
|
||||||
return at + needle.len / 2;
|
return at + needle.len / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -706,7 +720,7 @@ test "a length prefix longer than the document swallows the rest of it" {
|
||||||
|
|
||||||
test "a blank line outside any value has nothing to hover" {
|
test "a blank line outside any value has nothing to hover" {
|
||||||
const text = "#!srfv1\n#!long\nname::alice\n\nname::bob\n";
|
const text = "#!srfv1\n#!long\nname::alice\n\nname::bob\n";
|
||||||
const blank_at = std.mem.indexOf(u8, text, "alice\n\n").? + 6;
|
const blank_at = std.mem.find(u8, text, "alice\n\n").? + 6;
|
||||||
try testing.expectEqual(@as(?Token, null), tokenAt(text, blank_at));
|
try testing.expectEqual(@as(?Token, null), tokenAt(text, blank_at));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -938,14 +952,27 @@ test "value kinds describe themselves for a message" {
|
||||||
try testing.expectEqualStrings("`binary`", ValueKind.bytes.describe());
|
try testing.expectEqualStrings("`binary`", ValueKind.bytes.describe());
|
||||||
}
|
}
|
||||||
|
|
||||||
test "in compact format the surplus stops at the next delimiter, as srf counts it" {
|
test "in compact format the surplus runs to the delimiter before the next colon" {
|
||||||
// srf's `checkShortPrefix` reports `indexOfScalar(past_val, ',')` bytes, so
|
// Mirrors srf's `likelyExtraBytes`. The nearest comma would give 2, making the
|
||||||
// measuring to end of line here would make hover suggest a different
|
// value "23,000" and the next key "000,000,really" -- a comma in a key is the
|
||||||
// corrected length than the diagnostic does.
|
// signature of a wrong guess. The colon after "really" is where the next
|
||||||
|
// field's key ends, so the comma just before it is the separator: 4 + 10 = 14.
|
||||||
const problem = problemAt("#!srfv1\nim_worth:4:23,000,000,000,really:bool:false\n", "im_worth").?;
|
const problem = problemAt("#!srfv1\nim_worth:4:23,000,000,000,really:bool:false\n", "im_worth").?;
|
||||||
try testing.expectEqual(@as(usize, 4), problem.overrun.declared);
|
try testing.expectEqual(@as(usize, 4), problem.overrun.declared);
|
||||||
// "23,0" then "00" then a comma: two surplus bytes, so 4 + 2 = 6.
|
try testing.expectEqual(@as(usize, 10), problem.overrun.surplus);
|
||||||
try testing.expectEqual(@as(usize, 2), problem.overrun.surplus);
|
}
|
||||||
|
|
||||||
|
test "in compact format a value holding both a delimiter and a colon" {
|
||||||
|
// Value "a:" declared, "a:b,c" most likely meant: 2 + 3 = 5.
|
||||||
|
const problem = problemAt("#!srfv1\nk:2:a:b,c,next::v\n", "k:2:").?;
|
||||||
|
try testing.expectEqual(@as(usize, 3), problem.overrun.surplus);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "in compact format a colon with no delimiter before it falls back" {
|
||||||
|
// The first colon sits inside the value's URL, so there is no comma before it
|
||||||
|
// and the fallback to the first comma is what gets this right: 3 + 5 = 8.
|
||||||
|
const problem = problemAt("#!srfv1\nk:3:http://x,next::v\n", "k:3:").?;
|
||||||
|
try testing.expectEqual(@as(usize, 5), problem.overrun.surplus);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "in compact format a surplus with no delimiter runs to end of line" {
|
test "in compact format a surplus with no delimiter runs to end of line" {
|
||||||
|
|
|
||||||
|
|
@ -312,11 +312,13 @@ fn writeLengthProblem(
|
||||||
// Hover is still where the detail belongs, so state the byte arithmetic
|
// Hover is still where the detail belongs, so state the byte arithmetic
|
||||||
// and the rule rather than just repeating that something is wrong.
|
// and the rule rather than just repeating that something is wrong.
|
||||||
.overrun => |o| {
|
.overrun => |o| {
|
||||||
|
// Worded so that one verb form reads correctly for both a single
|
||||||
|
// surplus byte and many.
|
||||||
try w.print(
|
try w.print(
|
||||||
"**Length mismatch:** declares {d} byte{s}, but {d} more byte{s} follow before the end of the field.\n\n",
|
"**Length mismatch:** declares {d} byte{s}, but the field runs {d} byte{s} longer.\n\n",
|
||||||
.{ o.declared, plural(o.declared), o.surplus, plural(o.surplus) },
|
.{ o.declared, plural(o.declared), o.surplus, plural(o.surplus) },
|
||||||
);
|
);
|
||||||
try w.print("Restating the length as {d} would cover them. ", .{o.declared + o.surplus});
|
try w.print("Restating the length as {d} would cover it. ", .{o.declared + o.surplus});
|
||||||
if (f.long_mode) {
|
if (f.long_mode) {
|
||||||
try w.writeAll(
|
try w.writeAll(
|
||||||
"A length-prefixed value must be followed by end of line, optionally after whitespace or a comment.\n\n",
|
"A length-prefixed value must be followed by end of line, optionally after whitespace or a comment.\n\n",
|
||||||
|
|
@ -442,7 +444,7 @@ const testing = std.testing;
|
||||||
|
|
||||||
/// Hovers at the midpoint of `needle` and returns the markdown.
|
/// Hovers at the midpoint of `needle` and returns the markdown.
|
||||||
fn hoverOn(text: []const u8, needle: []const u8) ![]const u8 {
|
fn hoverOn(text: []const u8, needle: []const u8) ![]const u8 {
|
||||||
const at = std.mem.indexOf(u8, text, needle) orelse return error.NeedleNotInText;
|
const at = std.mem.find(u8, text, needle) orelse return error.NeedleNotInText;
|
||||||
const position = positions.positionFromByte(text, at + needle.len / 2, .@"utf-8");
|
const position = positions.positionFromByte(text, at + needle.len / 2, .@"utf-8");
|
||||||
// An error beats a panic here: hovering punctuation legitimately returns
|
// An error beats a panic here: hovering punctuation legitimately returns
|
||||||
// null, and a test that lands there should say so rather than abort.
|
// null, and a test that lands there should say so rather than abort.
|
||||||
|
|
@ -454,7 +456,7 @@ fn hoverOn(text: []const u8, needle: []const u8) ![]const u8 {
|
||||||
fn expectHoverContains(text: []const u8, needle: []const u8, expected: []const u8) !void {
|
fn expectHoverContains(text: []const u8, needle: []const u8, expected: []const u8) !void {
|
||||||
const markdown = try hoverOn(text, needle);
|
const markdown = try hoverOn(text, needle);
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
if (std.mem.indexOf(u8, markdown, expected) == null) {
|
if (std.mem.find(u8, markdown, expected) == null) {
|
||||||
std.debug.print("\nhovering \"{s}\"\nexpected to contain: {s}\ngot:\n{s}\n", .{ needle, expected, markdown });
|
std.debug.print("\nhovering \"{s}\"\nexpected to contain: {s}\ngot:\n{s}\n", .{ needle, expected, markdown });
|
||||||
return error.MissingFromHover;
|
return error.MissingFromHover;
|
||||||
}
|
}
|
||||||
|
|
@ -566,8 +568,8 @@ test "currency in a num is judged by srf, not by us" {
|
||||||
const text = "#!srfv1\n#!long\ncost:num:¥15,000\n";
|
const text = "#!srfv1\n#!long\ncost:num:¥15,000\n";
|
||||||
const markdown = try hoverOn(text, "15,000");
|
const markdown = try hoverOn(text, "15,000");
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
const parsed = std.mem.indexOf(u8, markdown, "Parsed as a number") != null;
|
const parsed = std.mem.find(u8, markdown, "Parsed as a number") != null;
|
||||||
const rejected = std.mem.indexOf(u8, markdown, "Does not parse") != null;
|
const rejected = std.mem.find(u8, markdown, "Does not parse") != null;
|
||||||
try testing.expect(parsed or rejected);
|
try testing.expect(parsed or rejected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -578,7 +580,7 @@ test "long mode is preserved when re-parsing, so commas stay in the value" {
|
||||||
|
|
||||||
test "hovering a comment gives nothing" {
|
test "hovering a comment gives nothing" {
|
||||||
const text = "#!srfv1\n# a comment\nk::v\n";
|
const text = "#!srfv1\n# a comment\nk::v\n";
|
||||||
const at = std.mem.indexOf(u8, text, "comment").?;
|
const at = std.mem.find(u8, text, "comment").?;
|
||||||
const position = positions.positionFromByte(text, at, .@"utf-8");
|
const position = positions.positionFromByte(text, at, .@"utf-8");
|
||||||
try testing.expectEqual(
|
try testing.expectEqual(
|
||||||
@as(?Hover, null),
|
@as(?Hover, null),
|
||||||
|
|
@ -605,7 +607,7 @@ test "hover reports the range of the thing it described" {
|
||||||
test "hover ranges use the negotiated encoding" {
|
test "hover ranges use the negotiated encoding" {
|
||||||
const text = "#!srfv1\n#!long\ncost:num:¥15,000\n";
|
const text = "#!srfv1\n#!long\ncost:num:¥15,000\n";
|
||||||
// Hover the value, whose line contains a two-byte character before it.
|
// Hover the value, whose line contains a two-byte character before it.
|
||||||
const at = std.mem.indexOf(u8, text, "15,000").?;
|
const at = std.mem.find(u8, text, "15,000").?;
|
||||||
|
|
||||||
const utf8 = (try hoverAt(
|
const utf8 = (try hoverAt(
|
||||||
testing.allocator,
|
testing.allocator,
|
||||||
|
|
@ -684,14 +686,14 @@ test "a long base64 text preview is truncated" {
|
||||||
|
|
||||||
const markdown = try hoverOn(text, b64);
|
const markdown = try hoverOn(text, b64);
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "decoding to 60 bytes") != null);
|
try testing.expect(std.mem.find(u8, markdown, "decoding to 60 bytes") != null);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "...") != null);
|
try testing.expect(std.mem.find(u8, markdown, "...") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "empty base64 decodes to nothing without a preview" {
|
test "empty base64 decodes to nothing without a preview" {
|
||||||
const markdown = try hoverOn("#!srfv1\nblob:binary:\n", "binary");
|
const markdown = try hoverOn("#!srfv1\nblob:binary:\n", "binary");
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "No value") != null);
|
try testing.expect(std.mem.find(u8, markdown, "No value") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "looksLikeText accepts text and rejects control bytes" {
|
test "looksLikeText accepts text and rejects control bytes" {
|
||||||
|
|
@ -705,7 +707,7 @@ test "looksLikeText accepts text and rejects control bytes" {
|
||||||
|
|
||||||
test "an overrun length in long format suggests the corrected count" {
|
test "an overrun length in long format suggests the corrected count" {
|
||||||
const text = "#!srfv1\n#!long\nk:3:hello\n";
|
const text = "#!srfv1\n#!long\nk:3:hello\n";
|
||||||
try expectHoverContains(text, "k:3:", "declares 3 bytes, but 2 more bytes follow");
|
try expectHoverContains(text, "k:3:", "declares 3 bytes, but the field runs 2 bytes longer");
|
||||||
try expectHoverContains(text, "k:3:", "Restating the length as 5");
|
try expectHoverContains(text, "k:3:", "Restating the length as 5");
|
||||||
try expectHoverContains(text, "k:3:", "optionally after whitespace or a comment");
|
try expectHoverContains(text, "k:3:", "optionally after whitespace or a comment");
|
||||||
}
|
}
|
||||||
|
|
@ -724,20 +726,20 @@ test "an overrun length in compact format explains the terminator rule" {
|
||||||
test "a trailing comment in long format is not reported as a mismatch" {
|
test "a trailing comment in long format is not reported as a mismatch" {
|
||||||
const markdown = try hoverOn("#!srfv1\n#!long\nk:5:hello # fine\n", "hello");
|
const markdown = try hoverOn("#!srfv1\n#!long\nk:5:hello # fine\n", "hello");
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "Length mismatch") == null);
|
try testing.expect(std.mem.find(u8, markdown, "Length mismatch") == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "a correct length prefix reports no mismatch" {
|
test "a correct length prefix reports no mismatch" {
|
||||||
const markdown = try hoverOn("#!srfv1\n#!long\nk:5:hello\n", "hello");
|
const markdown = try hoverOn("#!srfv1\n#!long\nk:5:hello\n", "hello");
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "Length mismatch") == null);
|
try testing.expect(std.mem.find(u8, markdown, "Length mismatch") == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "a correct length prefix with commas in compact format reports no mismatch" {
|
test "a correct length prefix with commas in compact format reports no mismatch" {
|
||||||
const markdown = try hoverOn("#!srfv1\nk:5:a,b,c,next::x\n", "a,b,c");
|
const markdown = try hoverOn("#!srfv1\nk:5:a,b,c,next::x\n", "a,b,c");
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "Length mismatch") == null);
|
try testing.expect(std.mem.find(u8, markdown, "Length mismatch") == null);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "a,b,c") != null);
|
try testing.expect(std.mem.find(u8, markdown, "a,b,c") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "an untyped numeric-looking value carries the coercion caveat" {
|
test "an untyped numeric-looking value carries the coercion caveat" {
|
||||||
|
|
@ -752,13 +754,13 @@ test "the caveat fires on the shape that broke a real consumer" {
|
||||||
test "a non-numeric string carries no caveat" {
|
test "a non-numeric string carries no caveat" {
|
||||||
const markdown = try hoverOn("#!srfv1\nname::alice\n", "alice");
|
const markdown = try hoverOn("#!srfv1\nname::alice\n", "alice");
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "strings_to_numbers") == null);
|
try testing.expect(std.mem.find(u8, markdown, "strings_to_numbers") == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "an explicitly typed number carries no caveat" {
|
test "an explicitly typed number carries no caveat" {
|
||||||
const markdown = try hoverOn("#!srfv1\nage:num:30\n", "30");
|
const markdown = try hoverOn("#!srfv1\nage:num:30\n", "30");
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "strings_to_numbers") == null);
|
try testing.expect(std.mem.find(u8, markdown, "strings_to_numbers") == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "an explicit string hint gets the declared wording" {
|
test "an explicit string hint gets the declared wording" {
|
||||||
|
|
@ -773,7 +775,7 @@ test "a length-prefixed numeric value gets the caveat too" {
|
||||||
test "a bool value carries no caveat" {
|
test "a bool value carries no caveat" {
|
||||||
const markdown = try hoverOn("#!srfv1\nk:bool:true\n", "true");
|
const markdown = try hoverOn("#!srfv1\nk:bool:true\n", "true");
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "strings_to_numbers") == null);
|
try testing.expect(std.mem.find(u8, markdown, "strings_to_numbers") == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "the caveat covers negative and floating point values" {
|
test "the caveat covers negative and floating point values" {
|
||||||
|
|
@ -784,7 +786,7 @@ test "the caveat covers negative and floating point values" {
|
||||||
test "a multi-line length-prefixed value carries no caveat" {
|
test "a multi-line length-prefixed value carries no caveat" {
|
||||||
const markdown = try hoverOn("#!srfv1\n#!long\nbio:7:foo\nbar\n", "foo");
|
const markdown = try hoverOn("#!srfv1\n#!long\nbio:7:foo\nbar\n", "foo");
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
try testing.expect(std.mem.indexOf(u8, markdown, "strings_to_numbers") == null);
|
try testing.expect(std.mem.find(u8, markdown, "strings_to_numbers") == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "the caveat appears on the key as well as the value" {
|
test "the caveat appears on the key as well as the value" {
|
||||||
|
|
@ -803,9 +805,28 @@ test "hovering the punctuation between key and value gives nothing" {
|
||||||
test "the caveat is one line, not a paragraph" {
|
test "the caveat is one line, not a paragraph" {
|
||||||
const markdown = try hoverOn("#!srfv1\nk::32\n", "k::");
|
const markdown = try hoverOn("#!srfv1\nk::32\n", "k::");
|
||||||
defer testing.allocator.free(markdown);
|
defer testing.allocator.free(markdown);
|
||||||
const at = std.mem.indexOf(u8, markdown, "Untyped, so this").?;
|
const at = std.mem.find(u8, markdown, "Untyped, so this").?;
|
||||||
const rest = markdown[at..];
|
const rest = markdown[at..];
|
||||||
const line_end = std.mem.indexOfScalar(u8, rest, '\n').?;
|
const line_end = std.mem.findScalar(u8, rest, '\n').?;
|
||||||
// A single sentence on a single line.
|
// A single sentence on a single line.
|
||||||
try testing.expect(line_end < 120);
|
try testing.expect(line_end < 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "hover's suggested length matches srf's diagnostic exactly" {
|
||||||
|
// The two are computed independently -- srf's `likelyExtraBytes` and our
|
||||||
|
// `likelySurplusEnd` -- so they can drift. These are the numbers srf emits for
|
||||||
|
// the same documents; if either side changes its rule, this fails.
|
||||||
|
try expectHoverContains(
|
||||||
|
"#!srfv1\nim_worth:4:23,000,000,000,really:bool:false\n",
|
||||||
|
"im_worth",
|
||||||
|
"Restating the length as 14",
|
||||||
|
);
|
||||||
|
try expectHoverContains("#!srfv1\nk:2:a:b,c,next::v\n", "k:2:", "Restating the length as 5");
|
||||||
|
try expectHoverContains("#!srfv1\nk:3:http://x,next::v\n", "k:3:", "Restating the length as 8");
|
||||||
|
try expectHoverContains("#!srfv1\nk:2:abc\n", "k:2:", "Restating the length as 3");
|
||||||
|
try expectHoverContains("#!srfv1\n#!long\nim_worth:4:23,000,000\n", "im_worth", "Restating the length as 10");
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a single surplus byte reads grammatically" {
|
||||||
|
try expectHoverContains("#!srfv1\nk:2:abc\n", "k:2:", "the field runs 1 byte longer");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ pub fn lineAt(text: []const u8, index: u32) []const u8 {
|
||||||
var remaining = text;
|
var remaining = text;
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
const nl = std.mem.indexOfScalar(u8, remaining, '\n');
|
const nl = std.mem.findScalar(u8, remaining, '\n');
|
||||||
if (i == index) {
|
if (i == index) {
|
||||||
const line = if (nl) |n| remaining[0..n] else remaining;
|
const line = if (nl) |n| remaining[0..n] else remaining;
|
||||||
return std.mem.trimEnd(u8, line, "\r");
|
return std.mem.trimEnd(u8, line, "\r");
|
||||||
|
|
@ -61,7 +61,7 @@ pub fn lineStart(text: []const u8, index: u32) usize {
|
||||||
var offset: usize = 0;
|
var offset: usize = 0;
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (i < index) : (i += 1) {
|
while (i < index) : (i += 1) {
|
||||||
const nl = std.mem.indexOfScalarPos(u8, text, offset, '\n') orelse return text.len;
|
const nl = std.mem.findScalarPos(u8, text, offset, '\n') orelse return text.len;
|
||||||
offset = nl + 1;
|
offset = nl + 1;
|
||||||
}
|
}
|
||||||
return @min(offset, text.len);
|
return @min(offset, text.len);
|
||||||
|
|
@ -131,7 +131,7 @@ pub fn positionFromByte(text: []const u8, offset: usize, encoding: Encoding) Pos
|
||||||
const limit = @min(offset, text.len);
|
const limit = @min(offset, text.len);
|
||||||
var line: u32 = 0;
|
var line: u32 = 0;
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (std.mem.indexOfScalarPos(u8, text, i, '\n')) |nl| {
|
while (std.mem.findScalarPos(u8, text, i, '\n')) |nl| {
|
||||||
if (nl >= limit) break;
|
if (nl >= limit) break;
|
||||||
line += 1;
|
line += 1;
|
||||||
i = nl + 1;
|
i = nl + 1;
|
||||||
|
|
@ -269,7 +269,7 @@ test "positionFromByte reports the right line" {
|
||||||
|
|
||||||
test "rangeFromSpan converts both ends" {
|
test "rangeFromSpan converts both ends" {
|
||||||
const text = "#!srfv1\nname::alice\n";
|
const text = "#!srfv1\nname::alice\n";
|
||||||
const at = std.mem.indexOf(u8, text, "alice").?;
|
const at = std.mem.find(u8, text, "alice").?;
|
||||||
const range = rangeFromSpan(text, at, at + 5, .@"utf-8");
|
const range = rangeFromSpan(text, at, at + 5, .@"utf-8");
|
||||||
try testing.expectEqual(@as(u32, 1), range.start.line);
|
try testing.expectEqual(@as(u32, 1), range.start.line);
|
||||||
try testing.expectEqual(@as(u32, 6), range.start.character);
|
try testing.expectEqual(@as(u32, 6), range.start.character);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue