diff --git a/build.zig b/build.zig index ba2f8404..7aaf14d0 100644 --- a/build.zig +++ b/build.zig @@ -107,6 +107,7 @@ pub fn build(b: *std.build.Builder) void { .target = target, .deps = example.packages, .res_dirs = if (example.has_assets) &.{"examples/" ++ example.name ++ "/assets"} else null, + .watch_paths = &.{"examples/" ++ example.name}, }, ); example_app.setBuildMode(mode); @@ -227,6 +228,7 @@ pub const App = struct { step: *std.build.LibExeObjStep, platform: Platform, res_dirs: ?[]const []const u8, + watch_paths: ?[]const []const u8, pub const Platform = enum { native, @@ -244,6 +246,7 @@ pub const App = struct { target: std.zig.CrossTarget, deps: ?[]const Pkg = null, res_dirs: ?[]const []const u8 = null, + watch_paths: ?[]const []const u8 = null, }) App { const target = (std.zig.system.NativeTargetInfo.detect(options.target) catch unreachable).target; const platform = Platform.fromTarget(target); @@ -297,6 +300,7 @@ pub const App = struct { .name = options.name, .platform = platform, .res_dirs = options.res_dirs, + .watch_paths = options.watch_paths, }; } @@ -367,14 +371,17 @@ pub const App = struct { const address = std.process.getEnvVarOwned(app.b.allocator, "MACH_ADDRESS") catch app.b.allocator.dupe(u8, "127.0.0.1") catch unreachable; const port = std.process.getEnvVarOwned(app.b.allocator, "MACH_PORT") catch app.b.allocator.dupe(u8, "8080") catch unreachable; const address_parsed = std.net.Address.resolveIp(address, std.fmt.parseInt(u16, port, 10) catch unreachable) catch unreachable; - const wasmserve = @import("tools/wasmserve/wasmserve.zig"); + const install_step_name = if (std.mem.startsWith(u8, app.step.name, "example-")) + app.step.name + else + null; const serve_step = wasmserve.serve( app.step, .{ - .install_step_name = app.step.name, + .install_step_name = install_step_name, .install_dir = web_install_dir, - .watch_paths = &.{"tools/wasmserve/wasmserve.zig"}, + .watch_paths = app.watch_paths, .listen_address = address_parsed, }, ) catch unreachable; diff --git a/tools/wasmserve/wasmserve.zig b/tools/wasmserve/wasmserve.zig index ff554cd0..b35f75a2 100644 --- a/tools/wasmserve/wasmserve.zig +++ b/tools/wasmserve/wasmserve.zig @@ -18,10 +18,10 @@ const esc = struct { }; pub const Options = struct { - install_step_name: []const u8 = "install", + install_step_name: ?[]const u8 = null, install_dir: ?build.InstallDir = null, - watch_paths: []const []const u8 = &.{}, - listen_address: net.Address = net.Address.initIp4([4]u8{ 127, 0, 0, 1 }, 8080), + watch_paths: ?[]const []const u8 = null, + listen_address: ?net.Address = null, }; pub fn serve(step: *build.LibExeObjStep, options: Options) !*Wasmserve { @@ -31,15 +31,15 @@ pub fn serve(step: *build.LibExeObjStep, options: Options) !*Wasmserve { .step = build.Step.init(.run, "wasmserve", step.builder.allocator, Wasmserve.make), .b = step.builder, .exe_step = step, - .install_step_name = options.install_step_name, + .install_step_name = options.install_step_name orelse step.builder.getInstallStep().name, .install_dir = install_dir, .install_dir_iter = try fs.cwd().makeOpenPathIterable(step.builder.getInstallPath(install_dir, ""), .{}), - .address = options.listen_address, + .address = options.listen_address orelse net.Address.initIp4([4]u8{ 127, 0, 0, 1 }, 8080), .subscriber = null, - .watch_paths = options.watch_paths, + .watch_paths = options.watch_paths orelse &.{"src"}, .mtimes = std.AutoHashMap(fs.File.INode, i128).init(step.builder.allocator), - .status = .idle, - .notify_msg = try step.builder.allocator.alloc(u8, 0), + .notify_msg = null, + .build_per_stop_count = 0, }; return self; } @@ -55,13 +55,18 @@ const Wasmserve = struct { subscriber: ?*net.StreamServer.Connection, watch_paths: []const []const u8, mtimes: std.AutoHashMap(fs.File.INode, i128), - status: Status, - notify_msg: []u8, + notify_msg: ?NotifyMessage, + build_per_stop_count: u5, - const Status = enum { - idle, - built, - build_error, + const NotifyMessage = struct { + const Event = enum { + built, + build_error, + stopped, + }; + + event: Event, + data: []const u8, }; pub fn make(step: *build.Step) !void { @@ -132,7 +137,9 @@ const Wasmserve = struct { _ = try conn.stream.write("HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: text/event-stream\r\nCache-Control: No-Cache\r\n\r\n"); self.subscriber = try self.b.allocator.create(net.StreamServer.Connection); self.subscriber.?.* = conn; - self.notify(); + if (self.notify_msg) |msg| + if (msg.event != .built) + self.notify(); return; } if (self.researchPath(url)) |file_path| { @@ -168,23 +175,7 @@ const Wasmserve = struct { "\r\n", .{ file_size, file_mime }, ); - - const zero_iovec = &[0]std.os.iovec_const{}; - var send_total: usize = 0; - while (true) { - const send_len = try std.os.sendfile( - stream.handle, - file.handle, - send_total, - file_size, - zero_iovec, - zero_iovec, - 0, - ); - if (send_len == 0) - break; - send_total += send_len; - } + try fs.File.writeFileAll(.{ .handle = stream.handle }, file, .{}); } fn respondError(stream: net.Stream, code: u32, desc: []const u8) !void { @@ -213,7 +204,7 @@ const Wasmserve = struct { timer_loop: while (true) : (std.time.sleep(100 * std.time.ns_per_ms)) { for (self.watch_paths) |path| { var dir = fs.cwd().openIterableDir(path, .{}) catch { - if (self.checkForUpdate(path)) |is_updated| { + if (self.checkForUpdate(fs.cwd(), path)) |is_updated| { if (is_updated) continue :timer_loop; } else |err| logErr(err, @src()); @@ -230,7 +221,7 @@ const Wasmserve = struct { continue; }) |walk_entry| { if (walk_entry.kind != .File) continue; - if (self.checkForUpdate(walk_entry.path)) |is_updated| { + if (self.checkForUpdate(dir.dir, walk_entry.path)) |is_updated| { if (is_updated) continue :timer_loop; } else |err| { @@ -242,8 +233,8 @@ const Wasmserve = struct { } } - fn checkForUpdate(self: *Wasmserve, path: []const u8) !bool { - const stat = try fs.cwd().statFile(path); + fn checkForUpdate(self: *Wasmserve, p_dir: fs.Dir, path: []const u8) !bool { + const stat = try p_dir.statFile(path); const entry = try self.mtimes.getOrPut(stat.inode); if (entry.found_existing and stat.mtime > entry.value_ptr.*) { std.log.info(esc.yellow ++ esc.underline ++ "{s}" ++ esc.reset ++ " updated", .{path}); @@ -257,17 +248,19 @@ const Wasmserve = struct { fn notify(self: *Wasmserve) void { if (self.subscriber) |s| { - s.stream.writer().print("event: {s}\n", .{@tagName(self.status)}) catch |err| logErr(err, @src()); - var lines = std.mem.split(u8, self.notify_msg, "\n"); - while (lines.next()) |line| - s.stream.writer().print("data: {s}\n", .{line}) catch |err| logErr(err, @src()); - _ = s.stream.write("\n") catch |err| logErr(err, @src()); - if (self.status == .built) self.status = .idle; + if (self.notify_msg) |msg| { + s.stream.writer().print("event: {s}\n", .{@tagName(msg.event)}) catch |err| logErr(err, @src()); + + var lines = std.mem.split(u8, msg.data, "\n"); + while (lines.next()) |line| + s.stream.writer().print("data: {s}\n", .{line}) catch |err| logErr(err, @src()); + _ = s.stream.write("\n") catch |err| logErr(err, @src()); + } } } fn compile(self: *Wasmserve) void { - std.log.info("Compiling...", .{}); + std.log.info("Building...", .{}); const res = std.ChildProcess.exec(.{ .allocator = self.b.allocator, .argv = &.{ self.b.zig_exe, "build", self.install_step_name, "--prominent-compile-errors", "--color", "on" }, @@ -276,16 +269,35 @@ const Wasmserve = struct { return; }; self.b.allocator.free(res.stdout); - self.b.allocator.free(self.notify_msg); - std.debug.print("{s}", .{res.stderr}); - self.notify_msg = res.stderr; + if (self.notify_msg) |msg| + if (msg.event == .build_error) + self.b.allocator.free(msg.data); + switch (res.term) { .Exited => |code| { - self.status = if (code == 0) .built else .build_error; + if (code == 0) { + std.log.info("Built", .{}); + self.notify_msg = .{ + .event = .built, + .data = "", + }; + } else { + std.log.err("Compile error", .{}); + self.notify_msg = .{ + .event = .build_error, + .data = res.stderr, + }; + } + }, + .Signal, .Stopped, .Unknown => { + std.log.err("The build process has stopped unexpectedly", .{}); + self.notify_msg = .{ + .event = .stopped, + .data = "", + }; }, - // TODO: separate status and message - else => {}, } + std.io.getStdErr().writeAll(res.stderr) catch |err| logErr(err, @src()); self.notify(); } }; diff --git a/tools/wasmserve/www/ansi_to_html.js b/tools/wasmserve/www/ansi_to_html.js new file mode 100644 index 00000000..16bd0529 --- /dev/null +++ b/tools/wasmserve/www/ansi_to_html.js @@ -0,0 +1 @@ +const defaults={fg:"#FFF",bg:"#000",newline:!1,stream:!1,colors:getDefaultColors()};function getDefaultColors(){const a={0:"#000",1:"#A00",2:"#0A0",3:"#A50",4:"#00A",5:"#A0A",6:"#0AA",7:"#AAA",8:"#555",9:"#F55",10:"#5F5",11:"#FF5",12:"#55F",13:"#F5F",14:"#5FF",15:"#FFF"};return range(0,5).forEach(b=>{range(0,5).forEach(c=>{range(0,5).forEach(d=>setStyleColor(b,c,d,a))})}),range(0,23).forEach(function(b){const c=toHexString(10*b+8);a[b+232]="#"+c+c+c}),a}function setStyleColor(a,c,d,e){const f=0b.length;)b="0"+b;return b}function toColorHexString(a){const b=[];for(const c of a)b.push(toHexString(c));return"#"+b.join("")}function generateOutput(a,b,c,d){let e;return"text"===b?e=pushText(c,d):"display"===b?e=handleDisplay(a,c,d):"xterm256Foreground"===b?e=pushForegroundColor(a,d.colors[c]):"xterm256Background"===b?e=pushBackgroundColor(a,d.colors[c]):"rgb"==b&&(e=handleRgb(a,c)),e}function handleRgb(a,b){b=b.substring(2).slice(0,-1);const c=+b.substr(0,2),d=b.substring(5).split(";"),e=d.map(function(a){return("0"+(+a).toString(16)).substr(-2)}).join("");return pushStyle(a,(38==c?"color:#":"background-color:#")+e)}function handleDisplay(a,b,c){b=parseInt(b,10);const d={"-1":()=>"
",0:()=>a.length&&resetStyles(a),1:()=>pushTag(a,"b"),3:()=>pushTag(a,"i"),4:()=>pushTag(a,"u"),8:()=>pushStyle(a,"display:none"),9:()=>pushTag(a,"strike"),22:()=>pushStyle(a,"font-weight:normal;text-decoration:none;font-style:normal"),23:()=>closeTag(a,"i"),24:()=>closeTag(a,"u"),39:()=>pushForegroundColor(a,c.fg),49:()=>pushBackgroundColor(a,c.bg),53:()=>pushStyle(a,"text-decoration:overline")};let e;return d[b]?e=d[b]():4b?e=pushTag(a,"blink"):29b?e=pushForegroundColor(a,c.colors[b-30]):39b?e=pushBackgroundColor(a,c.colors[b-40]):89b?e=pushForegroundColor(a,c.colors[8+(b-90)]):99b&&(e=pushBackgroundColor(a,c.colors[8+(b-100)])),e}function resetStyles(a){const b=a.slice(0);return a.length=0,b.reverse().map(function(a){return""}).join("")}function range(a,b){const c=[];for(let d=a;d<=b;d++)c.push(d);return c}function notCategory(a){return function(b){return(null===a||b.category!==a)&&"all"!==a}}function categoryForCode(a){a=parseInt(a,10);let b=null;return 0===a?b="all":1===a?b="bold":2a?b="underline":4a?b="blink":8===a?b="hide":9===a?b="strike":29a||39===a||89a?b="foreground-color":(39a||49===a||99a)&&(b="background-color"),b}function pushText(a){return a}function pushTag(a,b,c){return c||(c=""),a.push(b),`<${b}${c?` style="${c}"`:""}>`}function pushStyle(a,b){return pushTag(a,"span",b)}function pushForegroundColor(a,b){return pushTag(a,"span","color:"+b)}function pushBackgroundColor(a,b){return pushTag(a,"span","background-color:"+b)}function closeTag(a,b){let c;if(a.slice(-1)[0]===b&&(c=a.pop()),c)return""}function tokenize(a,b,c){function d(){return""}function e(a){return b.newline?c("display",-1):c("text",a),""}function f(b,c){c>h&&g||(g=!1,a=a.replace(b.pattern,b.sub))}let g=!1;const h=3,j=[{pattern:/^\x08+/,sub:d},{pattern:/^\x1b\[[012]?K/,sub:d},{pattern:/^\x1b\[\(B/,sub:d},{pattern:/^\x1b\[[34]8;2;\d+;\d+;\d+m/,sub:function(a){return c("rgb",a),""}},{pattern:/^\x1b\[38;5;(\d+)m/,sub:function(a,b){return c("xterm256Foreground",b),""}},{pattern:/^\x1b\[48;5;(\d+)m/,sub:function(a,b){return c("xterm256Background",b),""}},{pattern:/^\n/,sub:e},{pattern:/^\r+\n/,sub:e},{pattern:/^\r/,sub:e},{pattern:/^\x1b\[((?:\d{1,3};?)+|)m/,sub:function(a,b){g=!0,0===b.trim().length&&(b="0"),b=b.trimRight(";").split(";");for(const d of b)c("display",d);return""}},{pattern:/^\x1b\[\d?J/,sub:d},{pattern:/^\x1b\[\d{0,3};\d{0,3}f/,sub:d},{pattern:/^\x1b\[?[\d;]{0,3}/,sub:d},{pattern:/^(([^\x1b\x08\r\n])+)/,sub:function(a){return c("text",a),""}}],k=[];let{length:l}=a;outer:for(;0{const e=generateOutput(b,a.token,a.data,c);e&&d.push(e)}),tokenize(a.join(""),c,(a,e)=>{const f=generateOutput(b,a,e,c);f&&d.push(f),c.stream&&(this.stickyStack=updateStickyStack(this.stickyStack,a,e))}),b.length&&d.push(resetStyles(b)),d.join("")}}export default new Filter; \ No newline at end of file diff --git a/tools/wasmserve/www/ansi_up.js b/tools/wasmserve/www/ansi_up.js deleted file mode 100644 index 0e18da51..00000000 --- a/tools/wasmserve/www/ansi_up.js +++ /dev/null @@ -1,416 +0,0 @@ -/* ansi_up.js - * author : Dru Nelson - * license : MIT - * http://github.com/drudru/ansi_up - * - * NOTE: modifed to work with browser JS modules - */ -var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { - if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } - return cooked; -}; -var PacketKind; -(function (PacketKind) { - PacketKind[PacketKind["EOS"] = 0] = "EOS"; - PacketKind[PacketKind["Text"] = 1] = "Text"; - PacketKind[PacketKind["Incomplete"] = 2] = "Incomplete"; - PacketKind[PacketKind["ESC"] = 3] = "ESC"; - PacketKind[PacketKind["Unknown"] = 4] = "Unknown"; - PacketKind[PacketKind["SGR"] = 5] = "SGR"; - PacketKind[PacketKind["OSCURL"] = 6] = "OSCURL"; -})(PacketKind || (PacketKind = {})); -export default (function () { - function AnsiUp() { - this.VERSION = "5.1.0"; - this.setup_palettes(); - this._use_classes = false; - this.bold = false; - this.italic = false; - this.underline = false; - this.fg = this.bg = null; - this._buffer = ''; - this._url_whitelist = { 'http': 1, 'https': 1 }; - } - Object.defineProperty(AnsiUp.prototype, "use_classes", { - get: function () { - return this._use_classes; - }, - set: function (arg) { - this._use_classes = arg; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(AnsiUp.prototype, "url_whitelist", { - get: function () { - return this._url_whitelist; - }, - set: function (arg) { - this._url_whitelist = arg; - }, - enumerable: false, - configurable: true - }); - AnsiUp.prototype.setup_palettes = function () { - var _this = this; - this.ansi_colors = - [ - [ - { rgb: [0, 0, 0], class_name: "ansi-black" }, - { rgb: [187, 0, 0], class_name: "ansi-red" }, - { rgb: [0, 187, 0], class_name: "ansi-green" }, - { rgb: [187, 187, 0], class_name: "ansi-yellow" }, - { rgb: [0, 0, 187], class_name: "ansi-blue" }, - { rgb: [187, 0, 187], class_name: "ansi-magenta" }, - { rgb: [0, 187, 187], class_name: "ansi-cyan" }, - { rgb: [255, 255, 255], class_name: "ansi-white" } - ], - [ - { rgb: [85, 85, 85], class_name: "ansi-bright-black" }, - { rgb: [255, 85, 85], class_name: "ansi-bright-red" }, - { rgb: [0, 255, 0], class_name: "ansi-bright-green" }, - { rgb: [255, 255, 85], class_name: "ansi-bright-yellow" }, - { rgb: [85, 85, 255], class_name: "ansi-bright-blue" }, - { rgb: [255, 85, 255], class_name: "ansi-bright-magenta" }, - { rgb: [85, 255, 255], class_name: "ansi-bright-cyan" }, - { rgb: [255, 255, 255], class_name: "ansi-bright-white" } - ] - ]; - this.palette_256 = []; - this.ansi_colors.forEach(function (palette) { - palette.forEach(function (rec) { - _this.palette_256.push(rec); - }); - }); - var levels = [0, 95, 135, 175, 215, 255]; - for (var r = 0; r < 6; ++r) { - for (var g = 0; g < 6; ++g) { - for (var b = 0; b < 6; ++b) { - var col = { rgb: [levels[r], levels[g], levels[b]], class_name: 'truecolor' }; - this.palette_256.push(col); - } - } - } - var grey_level = 8; - for (var i = 0; i < 24; ++i, grey_level += 10) { - var gry = { rgb: [grey_level, grey_level, grey_level], class_name: 'truecolor' }; - this.palette_256.push(gry); - } - }; - AnsiUp.prototype.escape_txt_for_html = function (txt) { - return txt.replace(/[&<>"']/gm, function (str) { - if (str === "&") - return "&"; - if (str === "<") - return "<"; - if (str === ">") - return ">"; - if (str === "\"") - return """; - if (str === "'") - return "'"; - }); - }; - AnsiUp.prototype.append_buffer = function (txt) { - var str = this._buffer + txt; - this._buffer = str; - }; - AnsiUp.prototype.get_next_packet = function () { - var pkt = { - kind: PacketKind.EOS, - text: '', - url: '' - }; - var len = this._buffer.length; - if (len == 0) - return pkt; - var pos = this._buffer.indexOf("\x1B"); - if (pos == -1) { - pkt.kind = PacketKind.Text; - pkt.text = this._buffer; - this._buffer = ''; - return pkt; - } - if (pos > 0) { - pkt.kind = PacketKind.Text; - pkt.text = this._buffer.slice(0, pos); - this._buffer = this._buffer.slice(pos); - return pkt; - } - if (pos == 0) { - if (len == 1) { - pkt.kind = PacketKind.Incomplete; - return pkt; - } - var next_char = this._buffer.charAt(1); - if ((next_char != '[') && (next_char != ']')) { - pkt.kind = PacketKind.ESC; - pkt.text = this._buffer.slice(0, 1); - this._buffer = this._buffer.slice(1); - return pkt; - } - if (next_char == '[') { - if (!this._csi_regex) { - this._csi_regex = rgx(__makeTemplateObject(["\n ^ # beginning of line\n #\n # First attempt\n (?: # legal sequence\n \u001B[ # CSI\n ([<-?]?) # private-mode char\n ([d;]*) # any digits or semicolons\n ([ -/]? # an intermediate modifier\n [@-~]) # the command\n )\n | # alternate (second attempt)\n (?: # illegal sequence\n \u001B[ # CSI\n [ -~]* # anything legal\n ([\0-\u001F:]) # anything illegal\n )\n "], ["\n ^ # beginning of line\n #\n # First attempt\n (?: # legal sequence\n \\x1b\\[ # CSI\n ([\\x3c-\\x3f]?) # private-mode char\n ([\\d;]*) # any digits or semicolons\n ([\\x20-\\x2f]? # an intermediate modifier\n [\\x40-\\x7e]) # the command\n )\n | # alternate (second attempt)\n (?: # illegal sequence\n \\x1b\\[ # CSI\n [\\x20-\\x7e]* # anything legal\n ([\\x00-\\x1f:]) # anything illegal\n )\n "])); - } - var match = this._buffer.match(this._csi_regex); - if (match === null) { - pkt.kind = PacketKind.Incomplete; - return pkt; - } - if (match[4]) { - pkt.kind = PacketKind.ESC; - pkt.text = this._buffer.slice(0, 1); - this._buffer = this._buffer.slice(1); - return pkt; - } - if ((match[1] != '') || (match[3] != 'm')) - pkt.kind = PacketKind.Unknown; - else - pkt.kind = PacketKind.SGR; - pkt.text = match[2]; - var rpos = match[0].length; - this._buffer = this._buffer.slice(rpos); - return pkt; - } - if (next_char == ']') { - if (len < 4) { - pkt.kind = PacketKind.Incomplete; - return pkt; - } - if ((this._buffer.charAt(2) != '8') - || (this._buffer.charAt(3) != ';')) { - pkt.kind = PacketKind.ESC; - pkt.text = this._buffer.slice(0, 1); - this._buffer = this._buffer.slice(1); - return pkt; - } - if (!this._osc_st) { - this._osc_st = rgxG(__makeTemplateObject(["\n (?: # legal sequence\n (\u001B\\) # ESC | # alternate\n (\u0007) # BEL (what xterm did)\n )\n | # alternate (second attempt)\n ( # illegal sequence\n [\0-\u0006] # anything illegal\n | # alternate\n [\b-\u001A] # anything illegal\n | # alternate\n [\u001C-\u001F] # anything illegal\n )\n "], ["\n (?: # legal sequence\n (\\x1b\\\\) # ESC \\\n | # alternate\n (\\x07) # BEL (what xterm did)\n )\n | # alternate (second attempt)\n ( # illegal sequence\n [\\x00-\\x06] # anything illegal\n | # alternate\n [\\x08-\\x1a] # anything illegal\n | # alternate\n [\\x1c-\\x1f] # anything illegal\n )\n "])); - } - this._osc_st.lastIndex = 0; - { - var match_1 = this._osc_st.exec(this._buffer); - if (match_1 === null) { - pkt.kind = PacketKind.Incomplete; - return pkt; - } - if (match_1[3]) { - pkt.kind = PacketKind.ESC; - pkt.text = this._buffer.slice(0, 1); - this._buffer = this._buffer.slice(1); - return pkt; - } - } - { - var match_2 = this._osc_st.exec(this._buffer); - if (match_2 === null) { - pkt.kind = PacketKind.Incomplete; - return pkt; - } - if (match_2[3]) { - pkt.kind = PacketKind.ESC; - pkt.text = this._buffer.slice(0, 1); - this._buffer = this._buffer.slice(1); - return pkt; - } - } - if (!this._osc_regex) { - this._osc_regex = rgx(__makeTemplateObject(["\n ^ # beginning of line\n #\n \u001B]8; # OSC Hyperlink\n [ -:<-~]* # params (excluding ;)\n ; # end of params\n ([!-~]{0,512}) # URL capture\n (?: # ST\n (?:\u001B\\) # ESC | # alternate\n (?:\u0007) # BEL (what xterm did)\n )\n ([ -~]+) # TEXT capture\n \u001B]8;; # OSC Hyperlink End\n (?: # ST\n (?:\u001B\\) # ESC | # alternate\n (?:\u0007) # BEL (what xterm did)\n )\n "], ["\n ^ # beginning of line\n #\n \\x1b\\]8; # OSC Hyperlink\n [\\x20-\\x3a\\x3c-\\x7e]* # params (excluding ;)\n ; # end of params\n ([\\x21-\\x7e]{0,512}) # URL capture\n (?: # ST\n (?:\\x1b\\\\) # ESC \\\n | # alternate\n (?:\\x07) # BEL (what xterm did)\n )\n ([\\x20-\\x7e]+) # TEXT capture\n \\x1b\\]8;; # OSC Hyperlink End\n (?: # ST\n (?:\\x1b\\\\) # ESC \\\n | # alternate\n (?:\\x07) # BEL (what xterm did)\n )\n "])); - } - var match = this._buffer.match(this._osc_regex); - if (match === null) { - pkt.kind = PacketKind.ESC; - pkt.text = this._buffer.slice(0, 1); - this._buffer = this._buffer.slice(1); - return pkt; - } - pkt.kind = PacketKind.OSCURL; - pkt.url = match[1]; - pkt.text = match[2]; - var rpos = match[0].length; - this._buffer = this._buffer.slice(rpos); - return pkt; - } - } - }; - AnsiUp.prototype.ansi_to_html = function (txt) { - this.append_buffer(txt); - var blocks = []; - while (true) { - var packet = this.get_next_packet(); - if ((packet.kind == PacketKind.EOS) - || (packet.kind == PacketKind.Incomplete)) - break; - if ((packet.kind == PacketKind.ESC) - || (packet.kind == PacketKind.Unknown)) - continue; - if (packet.kind == PacketKind.Text) - blocks.push(this.transform_to_html(this.with_state(packet))); - else if (packet.kind == PacketKind.SGR) - this.process_ansi(packet); - else if (packet.kind == PacketKind.OSCURL) - blocks.push(this.process_hyperlink(packet)); - } - return blocks.join(""); - }; - AnsiUp.prototype.with_state = function (pkt) { - return { bold: this.bold, italic: this.italic, underline: this.underline, fg: this.fg, bg: this.bg, text: pkt.text }; - }; - AnsiUp.prototype.process_ansi = function (pkt) { - var sgr_cmds = pkt.text.split(';'); - while (sgr_cmds.length > 0) { - var sgr_cmd_str = sgr_cmds.shift(); - var num = parseInt(sgr_cmd_str, 10); - if (isNaN(num) || num === 0) { - this.fg = this.bg = null; - this.bold = false; - this.italic = false; - this.underline = false; - } - else if (num === 1) { - this.bold = true; - } - else if (num === 3) { - this.italic = true; - } - else if (num === 4) { - this.underline = true; - } - else if (num === 22) { - this.bold = false; - } - else if (num === 23) { - this.italic = false; - } - else if (num === 24) { - this.underline = false; - } - else if (num === 39) { - this.fg = null; - } - else if (num === 49) { - this.bg = null; - } - else if ((num >= 30) && (num < 38)) { - this.fg = this.ansi_colors[0][(num - 30)]; - } - else if ((num >= 40) && (num < 48)) { - this.bg = this.ansi_colors[0][(num - 40)]; - } - else if ((num >= 90) && (num < 98)) { - this.fg = this.ansi_colors[1][(num - 90)]; - } - else if ((num >= 100) && (num < 108)) { - this.bg = this.ansi_colors[1][(num - 100)]; - } - else if (num === 38 || num === 48) { - if (sgr_cmds.length > 0) { - var is_foreground = (num === 38); - var mode_cmd = sgr_cmds.shift(); - if (mode_cmd === '5' && sgr_cmds.length > 0) { - var palette_index = parseInt(sgr_cmds.shift(), 10); - if (palette_index >= 0 && palette_index <= 255) { - if (is_foreground) - this.fg = this.palette_256[palette_index]; - else - this.bg = this.palette_256[palette_index]; - } - } - if (mode_cmd === '2' && sgr_cmds.length > 2) { - var r = parseInt(sgr_cmds.shift(), 10); - var g = parseInt(sgr_cmds.shift(), 10); - var b = parseInt(sgr_cmds.shift(), 10); - if ((r >= 0 && r <= 255) && (g >= 0 && g <= 255) && (b >= 0 && b <= 255)) { - var c = { rgb: [r, g, b], class_name: 'truecolor' }; - if (is_foreground) - this.fg = c; - else - this.bg = c; - } - } - } - } - } - }; - AnsiUp.prototype.transform_to_html = function (fragment) { - var txt = fragment.text; - if (txt.length === 0) - return txt; - txt = this.escape_txt_for_html(txt); - if (!fragment.bold && !fragment.italic && !fragment.underline && fragment.fg === null && fragment.bg === null) - return txt; - var styles = []; - var classes = []; - var fg = fragment.fg; - var bg = fragment.bg; - if (fragment.bold) - styles.push('font-weight:bold'); - if (fragment.italic) - styles.push('font-style:italic'); - if (fragment.underline) - styles.push('text-decoration:underline'); - if (!this._use_classes) { - if (fg) - styles.push("color:rgb(" + fg.rgb.join(',') + ")"); - if (bg) - styles.push("background-color:rgb(" + bg.rgb + ")"); - } - else { - if (fg) { - if (fg.class_name !== 'truecolor') { - classes.push(fg.class_name + "-fg"); - } - else { - styles.push("color:rgb(" + fg.rgb.join(',') + ")"); - } - } - if (bg) { - if (bg.class_name !== 'truecolor') { - classes.push(bg.class_name + "-bg"); - } - else { - styles.push("background-color:rgb(" + bg.rgb.join(',') + ")"); - } - } - } - var class_string = ''; - var style_string = ''; - if (classes.length) - class_string = " class=\"" + classes.join(' ') + "\""; - if (styles.length) - style_string = " style=\"" + styles.join(';') + "\""; - return "" + txt + ""; - }; - ; - AnsiUp.prototype.process_hyperlink = function (pkt) { - var parts = pkt.url.split(':'); - if (parts.length < 1) - return ''; - if (!this._url_whitelist[parts[0]]) - return ''; - var result = "" + this.escape_txt_for_html(pkt.text) + ""; - return result; - }; - return AnsiUp; -}()); -function rgx(tmplObj) { - var subst = []; - for (var _i = 1; _i < arguments.length; _i++) { - subst[_i - 1] = arguments[_i]; - } - var regexText = tmplObj.raw[0]; - var wsrgx = /^\s+|\s+\n|\s*#[\s\S]*?\n|\n/gm; - var txt2 = regexText.replace(wsrgx, ''); - return new RegExp(txt2); -} -function rgxG(tmplObj) { - var subst = []; - for (var _i = 1; _i < arguments.length; _i++) { - subst[_i - 1] = arguments[_i]; - } - var regexText = tmplObj.raw[0]; - var wsrgx = /^\s+|\s+\n|\s*#[\s\S]*?\n|\n/gm; - var txt2 = regexText.replace(wsrgx, ''); - return new RegExp(txt2, 'g'); -} diff --git a/tools/wasmserve/www/wasmserve.js b/tools/wasmserve/www/wasmserve.js index e9592bf9..5443ec55 100644 --- a/tools/wasmserve/www/wasmserve.js +++ b/tools/wasmserve/www/wasmserve.js @@ -1,5 +1,4 @@ -import AnsiUp from "./ansi_up.js"; -const ansiup = new AnsiUp; +import ansi_to_html from "./ansi_to_html.js"; let evtSource = new EventSource("/notify"); @@ -8,25 +7,35 @@ function setup() { window.location.reload(); }); evtSource.addEventListener("build_error", function (e) { - if (document.getElementById("error-screen") == null) { - let es = document.createElement("div"); - es.id = "error-screen"; - es.style.cssText = error_screen_css; - let h2 = document.createElement("h2"); - let pre = document.createElement("pre"); - h2.textContent = "An error occurred while building:"; - h2.style.cssText = error_screen_h2_css; - pre.innerHTML = ansiup.ansi_to_html(e.data); - pre.style.cssText = error_screen_pre_css; - es.appendChild(h2); - es.appendChild(pre); - document.body.appendChild(es); - console.error(e.data); - } else { - document.getElementById("error-screen"). - getElementsByTagName("pre").innerHTML = ansiup.ansi_to_html(e.data); - } + createErrorScreen("An error occurred while building:", e.data) }); + evtSource.addEventListener("stopped", function (e) { + createErrorScreen("The build process has stopped unexpectedly:", e.data) + }); +} + +function createErrorScreen(msg, data) { + if (document.getElementById("error-screen") == null) { + let es = document.createElement("div"); + es.id = "error-screen"; + es.style.cssText = error_screen_css; + let h2 = document.createElement("h2"); + let pre = document.createElement("pre"); + h2.textContent = msg; + h2.style.cssText = error_screen_h2_css; + pre.innerHTML = ansi_to_html.toHtml(data); + pre.style.cssText = error_screen_pre_css; + es.appendChild(h2); + es.appendChild(pre); + document.body.appendChild(es); + + // atm ANSI escape codes only works in chromium based browsers + if (!!window.chrome) + console.log(data); + } else { + document.getElementById("error-screen"). + getElementsByTagName("pre").innerHTML = ansi_to_html.toHtml(data); + } } const error_screen_css = @@ -35,23 +44,22 @@ const error_screen_css = "height: 100vh;" + "top: 0;" + "left: 0;" + - "background: rgba(0, 0, 0, 0.85);" + + "background: rgba(0, 0, 0, 0.8);" + "font-family: system-ui, monospace;" + "font-size: 16pt;" + "padding: 20px;" + - "box-sizing: border-box;"; - -const error_screen_h2_css = + "box-sizing: border-box;" + "color: white;" + - "margin-top: 0;"; + "z-index: 1;"; + +const error_screen_h2_css = "margin-top: 0;"; const error_screen_pre_css = - "border: 2px solid rgb(205, 92, 92);" + + "border-top: 8px solid #A00;" + "padding: 10px;" + - "background: rgba(0, 0, 0, 0.5);" + + "background: black;" + "font-size: 12pt;" + "white-space: pre-wrap;" + - "overflow: hidden;" + - "color: lightgray;"; + "overflow: hidden;"; export default setup;