All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
-
The language server now offers a
Quote all literals in listcode action that single-quotes every bare literal in the innermost list containing the cursor. -
Functions
longestCommonPrefix: Longest leading substring shared by every string in a list.([str] -- str)whenJust: Run a quotation on the inner value for its side effects when the Maybe is Just; does nothing on None.(Maybe[a] (a -- ) -- )setenv: Set an environment variable by name, taking the value then the name. Use when the name is not known statically; otherwise prefer$NAME!.(str str -- )stdinIsTerminal,stdoutIsTerminal, andstderrIsTerminal: Report whether the current effective standard stream is connected to a terminal or Windows console. Regular files, pipes, captures, and non-file streams return false. Redirections and symlinks are classified by their opened target.
-
The GitHub action can now install unreleased builds: passing a commit SHA or branch name as
versionclones the repository at that ref and builds from source with Go. Release tags (vX.Y.Z) andlateststill download pre-built binaries. -
The tar write functions (
tarDirInc,tarDirExc,tarPack) now accept a dictionary destination{path: str|path, compress?: bool}, wherecompressoverrides the extension-based gzip inference in either direction — useful for destinations without a meaningful extension (e.g.redo's$3temp files). -
Stream merge redirects
2>&1(stderr to stdout's destination) and1>&2(stdout to stderr's destination). Each is a single token with no internal spaces, and works on command lists, pipeline stages, and quotations. Unlike POSIX, they are not order-sensitive fd duplication: the merged stream follows the other stream's final destination, so2>&1 *captures both streams interleaved and[[make] 2>&1 [grep err]] |;sends stderr through the pipe, cross-platform. -
CLI completions for
cargo: subcommands (including installed third-party ones viacargo --list), per-subcommand options, and dynamic values for--target,--features,-p/--package,--bin/--example/--test/--benchtarget names, and installed crates forcargo uninstall. -
Match arms may list several literals in a row, matching if the subject equals any of them (OR), e.g.
'-h' '--help' : .... All alternatives in one arm must be the same literal kind: all strings, all integers, or all paths. -
Optional
followRedirectskey (bool, defaulttrue) on thehttpGet/httpPostrequest dictionary. Set it tofalseto get the first response back as-is instead of following redirects, e.g. to inspect theLocationorSet-Cookieheaders of a3xxresponse after a login POST. -
Octal, hexadecimal, and binary integer literals via
0o,0x, and0bprefixes (case-insensitive), e.g.0o644,0xFF,0b101. The base is purely a way of writing the literal; the value is an ordinary integer and prints in decimal. There are no digit separators. -
Functions
toBase/fromBase: format an integer in / parse a string from an arbitrary base (2–36).fromBasereturnsMaybe[int].toHex/toOctal/toBinandparseHex/parseOctal/parseBin: convenience wrappers overtoBase/fromBasefor the common bases.tarDirInc/tarDirExc/tarPack/tarList/tarExtract/tarExtractEntry/tarRead: create, list, extract, and read.tararchives, mirroring the existingzip*functions (same argument order and option dicts). Compression is chosen from the destination extension when writing (.tar.gz/.tgz→ gzip,.tar→ uncompressed) and auto-detected from the gzip magic bytes when reading, so.tar.gzis handled transparently. Symlinks are preserved on pack and recreated on extract (with a guard against targets escaping the destination); hard links and device nodes are rejected.
-
Optional
maxByteskey (int, default0= unlimited) on thezipExtract/zipExtractEntry/tarExtract/tarExtractEntryoptions dict: caps the total uncompressed bytes written during an extraction to guard against decompression bombs. -
Archive extraction (both
zip*andtar*) now refuses to write through a symlink that already exists in the destination directory and points outside it, closing a path-traversal vector when extracting into a directory that contains symlinks. -
Archive extraction (both
zip*andtar*) no longer follows a symlink at the final path component: regular files are created withO_EXCL, and inoverwritemode an existing name is unlinked (never dereferenced) before a fresh file is created. This mirrors GNU tar's behavior and prevents anoverwriteextraction from writing through a pre-existing symlink at the destination name (e.g.dest/report->/etc/passwd). -
Archive extraction (both
zip*andtar*) now performs every write through anos.Rootanchored at the destination directory. The kernel enforces that no path can escape the destination via..or a symlink component (usingopenat2/RESOLVE_BENEATHon Linux), closing the time-of-check/time-of-use race that a purely lexical containment check leaves open. Legitimate symlinks that stay within the destination continue to work. -
Optional fields in dictionary shape types, written
name?: T(and"name"?: Tindefsignatures). An optional field may be absent from a value; when present, its value is still type-checked. This lets option-style APIs be typed precisely instead of as a loose{v}dict — e.g.numFmt,httpGet/httpPost, gridgroupByaggregation specs, and thezip*option dicts now declare their required and optional keys. A required value satisfies an optional parameter, but an optional value does not satisfy a required one. -
The type checker now tracks the value of a string literal (as a
strrefinement) so agetwith a known key resolves a shape field the same way the:namegetter does:resp "body" getyields the declaredbodyfield's type instead of the union of every field type, sohttpGet? "body" get?type-checks asbytes. Because the key rides the stack as a type, it resolves even when the literal reachesgetthrough a variable; a key computed at runtime still returns the genericMaybe[value]. -
The language server now reports an informational diagnostic when a
?unwrap is statically guaranteed to fail — unwrapping a getter (:k?) for a field a concrete shape does not declare, or unwrapping a barenone. The hint is placed on the?and fires even when the value flows through a variable first (e.g.:b val! @val ?). Homogeneous dictionaries ({str: T}) return a genuineMaybe[T]for any key and are never flagged, and a value with a declaredMaybe[T]type is never flagged. -
Functions
clip: Copy a string to the system clipboard. Cross-platform, usingpbcopyon macOS,clipon Windows, andwl-copy/xclip/xselon Linux.(str -- )uuid: Generate a random (version 4) UUID per RFC 9562 as a canonical lowercase hyphenated string.( -- str)uuid7: Generate a time-ordered (version 7) UUID per RFC 9562, whose leading bits encode a Unix millisecond timestamp so values sort chronologically.( -- str)intCmp: Compare two ints and return -1, 0, or 1. Useful withsortByCmp.(int int -- int)dateTimeCmp: Compare two datetimes and return -1, 0, or 1. Useful withsortByCmp.(datetime datetime -- int)
-
unsetenv: Remove an environment variable by name. Unsetting a variable that does not exist is not an error.(str -- ) -
modTime: Return a file's last modification time as adatetime, the one file timestamp portable across operating systems and filesystems. Returns aMaybe(Nonewhen the file is missing or cannot be stat'd).(str|path -- Maybe[datetime]) -
The language server now offers completion on
$environment variables, drawing from the actual process environment as well as any environment variables already referenced in the current file. -
matcharms can now bind the matched value when matching on a type keyword by following it with a name, e.g.str s : @s len(mirroringjust v). Works for every type keyword (int,float,str,bool,list,dict,path,date,quotation,maybe,binary). -
A new
nulltype representing the JSON null value, distinct fromnone(the empty case ofMaybe).parseJsonnow producesnullfor JSONnull, thenullliteral pushes one, andnullcan be used in union types (e.g.int | null) and matched with anullarm.( -- null) -
Type checking v1!
- Quotes built from overloaded builtins whose arms all produce the same
output (e.g. the
str|pathfile ops likecd,toPath,readFile) now infer as a single union-input quote instead of an overloaded one, so they can be used directly asiff/loopbranch quotes (e.g.… (drop) (cd) iff).
- Quotes built from overloaded builtins whose arms all produce the same
output (e.g. the
-
File manager yank bindings that copy to the system clipboard via
wl-copy/xclip/xsel/pbcopy/clip:yf— copy the selected entry's file nameyp— copy the selected entry's absolute pathyg— copy the selected entry's path relative to the enclosing.gitdirectory
-
File manager popup that lists available follow-up keys whenever a multi-key prefix (
y,g) is pending -
Grid (data frame) type with columnar storage for high-performance tabular data
- Literal syntax:
[| col1, col2; val1, val2; val3, val4 |] - Optional grid and column metadata
- Typed column storage (int, float, string, datetime) with automatic optimization
GridViewfor filtered views without data copyingGridRowfor lazy row access without allocation
- Literal syntax:
-
Extended
mapto work with Grid and GridView (transforms rows using quotation returning dict) -
Extended
lento work with Grid, GridView, and GridRow -
Extended
getand:getter to work with GridRow -
Functions
gridRows- get row countgridCols- get list of column namesgridMeta- get grid-level metadatagridColMeta- get column metadatagridCol- extract a column as a listgridAddCol- add a columngridRemoveCol- remove a columngridRenameCol- rename a columngridSetCell- set a single cell valuegridValues- extract grid values as row-major lists without headersgridCompact- materialize a GridView to a Gridselect- project a grid to a specific ordered set of columnsexclude- drop a set of columns from a gridderive- append a derived column to a gridgroupBy- group grids by key columns with multiple aggregation specs, and preserve existing list grouping behaviorpivot- reshape a Grid or GridView into a pivot table; rows are grouped byrowKeys, distinctcolKeyvalues become new columns ordered by version-sort, and each cell aggregates matching source rows (empty cells fill withnone)updateCol- mutate a grid column by applying a quotation to each celltoGrid- build a grid from[[str]]with headers on the first rowjoin(grid form) - inner equi-join of two grids via key-extractor quotations; polymorphic with the existing stringjoinleftJoin- left outer equi-join of two gridsouterJoin- full outer equi-join of two gridsfilter- now a built-in that works on both Lists and Grids/GridViewseach- now a built-in that works on both Lists and Grids/GridViewstoDict- convert a GridRow to a dictionary+andextendfor vertical concatenation of Grids/GridViews. Strict matching by name (left-grid order wins); type mismatch produces a generic column with no numeric promotion; meta merges left-wins.+deep-copies;extendmutates the receiver in place, widening to generic when needed, and accepts a GridView in either position (the underlying source grid grows and the view's indices extend to include the new rows).
-
CLI
msh edit initto open the current init file path using$EDITOR, with fallback to the platform default opener when$EDITORis unavailable--type-check-onlyto run static type checking and exit without evaluating the script
-
Functions
toCsvCelltoCsvlinearSearchIndexid/2id/3id- identity quotes useful as no-op value selectors forlistToDictand similarparseExcel- parse an.xlsxworkbook into a list of sheets in workbook (tab) order; each sheet is a dict with anamekey, adatakey holding a rectangular list of rows, ahiddenkey (bool), and avisibilitykey ("visible"/"hidden"/"veryHidden")sortBy- stable ascending sort of a Grid or GridView by one or more columns; bare-string and list-of-strings forms;nonecells sort last; cross-type values in a generic column errorsortByCmpextended to accept Grid or GridView; the comparator receives twoGridRowsreverseis now a built-in and accepts list, Grid, or GridView (the prior std libreversedefinition is removed; behavior on lists is unchanged)
-
LSP completion at a literal outside of
[ ... ]argv lists now offers in-file definitions, standard library definitions, typed builtins, and remainingBuiltInListnames, with each item's signature (when known) shown as the completion detail. PATH-binary completion at the first position inside[ ... ]is unchanged.
-
A number immediately followed by a literal character now lexes as a single literal token instead of a float/int plus a separate literal, so bare file arguments like
redo 1.pdfwork. Floats still end at token-ending characters, e.g.(1.5),[2.5],3.5;, and4.5,lex as floats. -
Each of stdout/stderr now has exactly one destination: combining two destinations on the same stream (e.g. capture
*plus file redirect>,&>plus2>, a second>, or a merge plus anything else on that stream) is now an error, caught both by the static type checker and at runtime. Previously the extra redirect was silently ignored (capture won over file redirects) or last-wins.2>&1combined with<>is also rejected. -
loopquotations now honor stderr redirects, append mode, and merges, and inherit the enclosing quotation's redirected streams (previously a loop body wrote straight to the terminal even inside a redirected quotation). -
Quotations accept only the redirects that don't change the stack: file redirects, stdin, and the merges. Captures (
*,*b,^,^b) on a quotation now give a clear error at both layers instead of falling into the multiplication error, and the type checker now rejects<>on a quotation (the runtime always did). Capture individual command lists instead, e.g.[[cmd1] [cmd2]] (* !) map. -
zipPackentries can now be a bare string or path in addition to the dictionary form. A bare entry adds the file or directory under its base name, keeping its own mode:([str | path | {path: str | path, archivePath?: str | path, mode?: int}] str | path -- ) -
Breaking: the type checker no longer accepts an empty quote
()as the predicate toany/all. Pass(id)instead, e.g.[true false] (id) any. Both now carry the single signature([T] (T -- bool) -- bool), matching theirstd.mshdefinitions. -
A command that cannot start (not found, permission denied, bad format, ...) run with
?or;no longer aborts the script. Instead,?leaves a negative exit code carrying the exact reason:-(256+errno)for POSIX start failures,-(1024+winerror)on Windows,-(128+signal)for a process killed by a signal (replacing the old flat-1),-255for a command not found onPATH, and-256when the OS error cannot be read. Negative codes never collide with a real exit status (0-255).!still stops on these, exitingmshitself with the conventional 127/126/128+N. -
Breaking:
keyValuesnow returns a list of{k, v}dictionaries instead of a list of two-element lists. Each pair has akfield holding the key and avfield holding the value, so the key and value types stay distinct (previously they were collapsed into a single shared type, which forced overload-resolution ambiguity downstream). Update existing callers from2unpack key!, value!topair! @pair :k? key!, @pair :v? value!(or use:k?/:v?directly). -
History, bin map, and interactive log storage now use the
$LOCALAPPDATA\mshdirectory on Windows instead of$LOCALAPPDATA\mshell, andXDG_DATA_HOMEhistory/bin map storage now uses the requiredmsh/subdirectory on Linux/macOS. You should be able to simply move the previous files over with no problems. -
msh bin editnow falls back to the platform default opener when$EDITORis unavailable -
File manager preview now short-circuits many more common binary extensions and shows detailed archive listings with human-readable sizes and
h:mm AM/PMtimes for.zipand.tar.gzarchives -
File manager now hides OneDrive's hidden
.849C9593-D756-4E56-8D6E-42412F2A707Bmetadata file from listings and directory previews -
On Windows, pressing
hat the root of a drive in the file manager now shows mounted drive letters so you can switch volumes. -
matcharm separators now control subject consumption explicitly::consumes the matched subject and:>preserves it, independent of pattern kind or bindings -
updateColnow acceptsGridView, materializes a newGridfrom the viewed rows, retypes the result columns, and leaves the backingGridunchanged. -
skipandtakenow work on strings using the same indexing logic as string slicing. -
Completely removed the concept of
o,oc, andos. -
abs,max,min,max2,min2, andsumare now runtime builtins with proper(int -- int) | (float -- float)overloads (and([int] -- int) | ([float] -- float)for the list-folding variants). They were previously stdlib defs whose float-only bodies crashed on int operands when called via the int overload.sumIntis kept as a stdlib alias for backwards compatibility. Mixed int+float overloads onmax2/min2have been removed since the runtime</>operators reject mixed numeric types. -
maxandminnow also work on a list ofDateTime, returning the latest/earliest element (([DateTime] -- DateTime)). -
lenruntime now accepts dictionaries (returns key count); the sig already permitted this. -
md5runtime now acceptsbytesinput directly, matching the listed overload. -
Dict type expressions now require an implicit (or
str) key.{V}and{str: V}are accepted; anything else ({int: V},{path: V}, etc.) is a parse error. Dict keys are alwaysstrat runtime, and the type system no longer pretends otherwise. Every dict-related builtin signature (keys,values,get,set,setd,getDef,map,filter,in,len,keyValues,listToDict) drops theKgeneric accordingly. -
Error loading startup files:now includes the script path, whether a version was pinned, the full MSHSTDLIB/MSHINIT and standard-location lookup order, and concrete resolution steps -
Tightened the grid form of
groupBy: the aggregation-spec list is typed as[{agg: (GridView -- V)}]instead of[{str: V}], so the requiredaggfield and its quotation shape are now enforced statically. The agg quote's output type is generic per element, so a single list may mix specs whose quotations return different scalar types. Width subtyping still allows the optionalnameandmetafields. -
[head ...rest](and any other spread in amatchlist pattern) now binds the rest as a zero-copy sub-slice of the source list. Appending to the rest still allocates a fresh backing array because cap equals len, so the source is never overwritten. The behavioral difference is thatsetAton the rest list now mutates the shared backing — historically rest was an independent copy. This makes recursive list-walking idioms (e.g.def f [head ...rest] : ... @rest f) run in linear time instead of O(N²); the previous copy was the dominant cost on large lists. -
Extended the
:namegetter (andgetbuilt-in) to acceptGridandGridView. On a grid the getter returns the named column asMaybe[[T]]— the materialized column when present,nonewhen the column is absent — makingg :n?a shorthand forg "n" gridCol. On aGridViewthe values are projected through the view's row indices. The type checker now resolves the element type from the grid's schema when known. The runtime error message for:on an unsupported type now listsGridandGridViewalongsidedictandGridRow. -
The type checker now rejects
pivotaggregation quotations whose return type resolves to a container ([T],{V}, shape,Grid,GridView,GridRow), mirroring the runtime constraint that pivoted cells must be scalars. The check fires only when the quote's output is concretely a container after substitution; if the output stays as an unconstrained type variable (e.g.(:foo?)quotes that infer through a synthesized fresh input), the call still type-checks and the runtime still catches it. -
Tightened the
w/wl/we/wlewrite-builtin type signatures to match the runtime:wl/wleare now(str -- ) | (int -- ), andw/weare now(str -- ) | (int -- ) | (bytes -- ). Previously these were typed as(T -- )and silently accepted floats, bools, datetimes, lists, etc. — all of which crash at runtime. Convert withstrfirst (1.5 str wl) for other types.
- The
pickstack operator was removed. Its stack effect depends on a runtime integer, so it could not be expressed in the static type checker, and it saw no real use.
-
Commands no longer fail with "Error reclaiming terminal control: ... no such process" when several mshell processes share one terminal, as under parallel build runners (
redo,make -j) or when a script is backgrounded. mshell now transfers terminal control only when it is itself the terminal's current foreground process group (the same gate bash and fish use), and a hand-back to a previous foreground group that has since exited falls back to mshell's own group instead of failing the command. A reclaim problem is now at most a warning on stderr; the command's own exit code always stands. -
A fast pipeline whose processes finished before mshell could transfer terminal control is no longer killed and reported as failed; the transfer is skipped, since the work is already done. Restoring terminal modes is now also protected from
SIGTTOU, which could previously stop the shell mid-cleanup when another process group owned the terminal. A failure while closing the retained pipeline terminal handle is likewise now a warning, never a failure of a pipeline whose commands succeeded. -
Attempted to formally improve the semantics of job control and terminal control on both Linux and Windows. Should fix potential bugs when running TUI programs from within mshell scripts.
-
On Windows, a command name containing a forward slash (e.g.
./script.msh) is now treated as a file reference instead of being searched for onPATH, matching the behavior on Linux/macOS. Previously only backslashes were recognized as path separators in command position on Windows, so cross-platform scripts invoking local scripts with./failed. -
Interactive programs now work as a stage of a pipeline. A command that drives the terminal (e.g.
... | nvim -,... | less,... | fzf) is no longer stopped on startup: every external stage of a pipeline is now placed in one shared process group that becomes the terminal's foreground group, instead of each stage getting its own group with only one (whichever started first) receiving the terminal. The pipeline leader now also waits for every stage to start before reaping itself, fixing an intermittentsetpgidrace that could drop a stage with an "operation not permitted" error and lose its output. -
The file manager preview now times out instead of hanging when a file is slow to read. Cloud-backed files (e.g. OneDrive "files on demand") could block the preview worker indefinitely while hydrating, freezing previews for every other entry; a slow preview now gives up after a few seconds and shows a placeholder.
-
Match arms that are not a recognized pattern form now produce a clear error listing the legal forms, instead of silently failing to bind (and later reporting a confusing "unknown identifier" in the arm body).
-
Type-checker diagnostics for unknown identifiers inside a
$"...{ }"format string interpolation now point at the interpolation's actual source location rather than line 1, column 1. -
The type checker now joins the arms of a
matchorif/elseblock into a single union post-state instead of treating each arm as an independent alternative typing. Previously, arms that left different types on the stack (e.g.match []: 0.0, _ :> sum end, which yieldsint | float) fanned out, and a later operation that was valid for only one arm let the whole program pass — hiding a real type error that would crash at runtime. Such usage is now reported. -
Overloaded built-ins now accept a union operand (such as the
int | floatamatch/ifjoin produces) when every member of the union is handled. The checker resolves the call for each member and yields the union of the results, soint | float toFloatgivesfloatandint | float { … } numFmtformats. An unsafe combination is still rejected — e.g.int | floatdivided by afloatfails, because theintcase has no matching overload. -
In the interactive
::CLI shorthand, bare literals in argument position are no longer turned into strings, so operators work again (e.g.:: numargs '*' globnow runsglobas the wildcard operator instead of passing the wordglob). The leading command name is still treated as a command, so an executable continues to win over a builtin of the same name (e.g.date,sort).
match ... endpattern matching syntax with value matching, type matching,_wildcard, maybe destructuring (just v/none), list destructuring ([a b ...rest]), and dict destructuring ({ 'key': v })mapon dictionaries (maps over values, preserving keys)- Functions
filterbuiltin now supports dictionaries, filtering by value while preserving keyscdhcdpfromUnixTimeMicrofromUnixTimeMillifromUnixTimeNanoprompttoUnixTimeMicrotoUnixTimeMillitoUnixTimeNanotoSvgPathStrunlinesCrLfscaleLinear
- Explicit version syntax (example:
VER "v0.13.0") and execution. You can now specify the exact version a script should run with and this will force the execution to use that interpreter and corresponding standard library. - Multiple cut/copy selections in file manager.
- CLI interactive command execution now switches to a fresh output line before parsing/evaluation, so lexer/parser errors do not render on the prompt line.
- Lexer
ERRORtokens now stop parsing immediately (including simple CLI parsing), preventing fall through to evaluation errors like unimplementedERRORtoken handling.
- Startup now loads both
std.mshandinit.mshfrom version directories (msh/<version>/...), keepsinit.mshoptional for implicit current-version startup unlessMSHINITis set, requires it forVERscripts, re-execsVERscripts withmsh-<version>when needed, and ignoresMSHSTDLIB/MSHINITforVERscripts. cartesiantype signature changed. Now is[[a]] [a] -- [[a]]. This make it easy to chain more than one Cartesian product. Usually start the chain off with empty[[]]as an identity element.
- File manager
lon a file now opens it: text files open in$EDITOR, binary/unreadable files open with the platform default (Start-Processon Windows,xdg-openon Linux,openon macOS)
completionDefsbuiltin: pushes a dictionary of completion definitions, keyed by command name with quotation valuesmshFileManagerbuiltin: pops a starting directory from the stack, opens the file manager, and cds to the final directory on exitmsh fmnow accepts an optional starting directory argument- Built-in file manager via
msh fmsubcommand and Ctrl-O in interactive mode- Dual-pane layout with directory listing and file/directory preview
- Vim-style navigation (
j/k,h/l,gg/G, Ctrl-u/Ctrl-d) - Search with
/, case-insensitive match highlighting,n/Nto cycle matches - Rename with
r, cursor positioned before extension, Ctrl-W word delete - Bookmarks with
m+ char to set,;+ char to jump - Editor integration with
e(uses$EDITOR) - Directory change on quit (Ctrl-O returns to shell in new directory)
- Version-sorted entries, directories first and colored blue
- Binary file detection for preview
- Preview caching for fast scrolling
- Cut/copy/paste buffer (
dcut,yycopy,ppaste,cclear) shared across instances - Delete to trash (
x) with confirmation, using platform-native trash msh fmprints final directory to stdout forcd "$(msh fm)"usage
- Tail-call optimization (TCO) for recursive definitions in tail position.
- Functions
sincostanarctanlnln2ln10powrandomrandomFixedrandomNormsqrtrandomTritempFileExt
- CLI Alt-D inserts the current date as
YYYY-MM-DD
- Windows CMD.EXE /C quoting now handles quoted commands with extra arguments (e.g., npm.cmd paths with spaces).
- Builds/releases now are pure Go, built with
CGO_ENABLED=0.
- Prefix quote syntax (
functionName. ... end) as an alternative to(...) functionName <>operator for in-place file modification. Reads file to stdin, writes stdout back on success. Example:[sort -u] `file.txt` <> !- Functions
chompcstToUtcfromOleDatetoOleDate__gitCompletion__sshCompletionstrCmpstrEscapereSplitlinearSearch
- Function definition metadata dictionaries in
defsignatures - Definition-based CLI completions via the
completemetadata key - CLI completions for:
mshgitfdrgssh
- CLI history prefix search on Ctrl-N/Ctrl-P (case-insensitive)
- Alt-. to cycle last argument from history in the CLI
- Bin map file and
msh binCLI commands for binary overrides msh completionssubcommand for bash, fish, nushell, and elvish- CLI syntax highlighting for environment variables
- GitHub Action for installing mshell in CI workflows
- Append stderr redirection with
2>> - Combined stdout/stderr redirection with
&>(truncate) and&>>(append) - Same-path detection when using
>and2>with identical paths (shares single file descriptor) - Full stderr redirection support for quotations (
2>,2>>,&>,&>>) - Null byte validation for redirection file paths and
cp/mvcommands
- CLI binary mode now converts literal redirect targets (e.g.,
cmd > file.txtconvertsfile.txtto a string for stdout, path for stdin) - CTRL-C now only kills the running subprocess instead of both the subprocess and the shell
- Breaking change:
@namenow only reads mshell variables and no longer falls back to environment variables; use$NAMEfor environment access. w/wenow accept binary input and write raw bytes to stdout/stderr.- Renamed
.stostack,.deftodefs,.envtoenv - Removed
.b(usebinPathsinstead)
- Functions
2each2tuplefloatCmpceilfloorleftPadlastIndexOfnumFmtpreserveIntoption fornumFmtnowdatenullDeviceenumerateenumerateNtakeWhiledropWhile2unpack2applytitlezipDirInczipDirExczipDirzipPackzipListzipExtractzipExtractEntryzipReadchunkrepeatreturntoJsonbase64encodebase64decode:shorthand forget
timeoutoption forhttpGetandhttpPost- Support for comma-separated variable stores (e.g.
a!, b!, c!) - LSP completion suggestions for
@variable references - LSP rename support for variables scoped to definitions and globals
- Default CTRL-F binding matching fish shell behavior
- Execution operators for capturing stdout/stderr as strings or binary (
*b,^,^b)
- Breaking change: renamed the builtin that returns the current datetime from
datetonow;datenow truncates a datetime to its date-only component. parseJsonnow accepts binary input and decodes it as UTF-8 before parsing.fileExistsnow uses golang os.Lstat instead of os.Stat, meaning if you have a broken symlink in Linux,fileExistswill now returntrueinstead offalse.- Slice semantics are slightly different. You now get a new backing array guaranteed for slice. This would come up if you did a partial slice (
0:n), and then extended that in a loop or map. You could then be "extending" into the same backing array, causing previous items in the loop to be overwritten. mvwill now allow moving a file path into a directory path. Previously had to be file to file.skipandtakeno longer throw exception whennis greater than the length of the list.- Input redirection can now accept binary data directly and stream it to stdin without string conversion.
- Fixed infinite loop in
versionSortCmpwhen non-digit after digit.
- Basic tab completion for the CLI
- Basic HTML parsing
- Start of VS code extension
httpGetandhttpPostfor making web requests.- Functions
isNoneparseHtmlabsPathbindfindByTagconcatcartesiangroupByreFindreFindAllIndexmd5eachWhilermftakeskip
- Handling of
.cmdand.bat - Now immediately close file when appending
mvmade more robust- Fixed broken line/columns in lexing
- Better handling of UTF-8 input
- Now return Maybes for conversions
- Removed
canParseDt getreturns Maybe- No escaping in path literals
- In JSON mappings, null now goes to
none, not 0. fileSizenow returns Maybe
- Basic CLI history
- Command completion in CLI
countSubStr,uniq,canParseDt,fromUnixTime,toUnixTime- Built-in Maybe type,
?operator for unwrapping
- Dict literal parsing
- Sorted output for
keysandvalues mapnow a built in function
!operator for executing external command, stopping on non-zero exit codeseq,toFixed, `round``- JSON handling
filesIn->lsDirtempFilepushes a path, not a string
- Bug in
unlines - Bad printing in certain cases for
.sand others.
startsWithandendsWithtempDirhardLinkisWeekend,isWeekday,dow,unixTimewriteFile,appendFilerm,mv,cpskipe,ec,esfilesIn,runtimesort,sortusha256sumcontinuekeywordzipreMatch,reReplace- dictionaries
PATHsearching
- Allow standard output redirection to any string-like item.
- Initial releases of the project.