import {parser as fishParser} from "./fish-parser.js" import { LRLanguage, indentNodeProp, foldNodeProp, foldInside, delimitedIndent, continuedIndent } from "@codemirror/language" import {styleTags, tags as t} from "@lezer/highlight" import {completeFromList, CompletionSource} from "@codemirror/autocomplete" /// A language provider for fish, based on the Lezer grammar in `fish.grammar`. export const fishLanguage = LRLanguage.define({ name: "fish", parser: fishParser.configure({ props: [ indentNodeProp.add({ CommandSubstitution: delimitedIndent({closing: ")"}), // Every block ends with the same word, so every block indents the same // way, which is the one thing fish made easier than the shell did. "IfStatement WhileStatement ForStatement SwitchStatement FunctionDefinition BeginBlock": continuedIndent({except: /^\s*(end|else)\b/}), CaseClause: continuedIndent({except: /^\s*(case|end)\b/}) }), foldNodeProp.add({ CommandSubstitution: foldInside, "IfStatement WhileStatement ForStatement SwitchStatement FunctionDefinition BeginBlock": foldInside }), styleTags({ "if else end": t.controlKeyword, "while for in switch case begin": t.controlKeyword, function: t.definitionKeyword, // `and`, `or` and `not` are commands rather than punctuation, and read // as the logic they are either way. "and or not": t.logicOperator, Comment: t.lineComment, String: t.string, RawString: t.string, Escape: t.escape, Glob: t.special(t.string), "SimpleCommand/CommandName": t.function(t.variableName), "FunctionDefinition/FunctionName": t.function(t.definition(t.variableName)), VariableName: t.definition(t.variableName), SimpleExpansion: t.variableName, Dollar: t.variableName, RedirectOp: t.controlOperator, "PipeOp Background": t.controlOperator, "AndOp OrOp": t.logicOperator, Terminator: t.separator, '"\\""': t.string, '"$(" "(" ")"': t.paren, "SubscriptOpen SubscriptClose": t.squareBracket }) ] }), languageData: { commentTokens: {line: "#"}, closeBrackets: {brackets: ["(", "[", "{", "'", '"']}, indentOnInput: /^\s*(end|else|case)$/ } }) // fish keeps a great deal in builtins that other shells leave to programs on // the path, `string` and `math` above all, so this list is most of the // language rather than a footnote to it. const builtins = [ "abbr", "alias", "and", "argparse", "begin", "bg", "bind", "block", "break", "breakpoint", "builtin", "case", "cd", "command", "commandline", "complete", "contains", "continue", "count", "dirh", "dirs", "echo", "else", "emit", "end", "eval", "exec", "exit", "false", "fg", "for", "funced", "funcsave", "function", "functions", "history", "if", "isatty", "jobs", "math", "nextd", "not", "or", "path", "popd", "prevd", "printf", "prompt_pwd", "pushd", "pwd", "random", "read", "realpath", "return", "set", "set_color", "source", "status", "string", "switch", "test", "time", "trap", "true", "type", "ulimit", "umask", "vared", "wait", "while" ].map(label => ({label, type: "function"})) // The variables fish sets for you. `status` is the last command's exit code, // and `argv` is a list rather than a string, which is the difference between // fish and every other shell here. const specials = [ "argv", "CMD_DURATION", "fish_pid", "history", "HOME", "hostname", "IFS", "last_pid", "PATH", "pipestatus", "PWD", "SHLVL", "status", "USER", "version" ].map(label => ({label, type: "constant"})) /// Autocompletion for fish's builtins and the variables it sets. export const fishCompletion: CompletionSource = completeFromList([...builtins, ...specials]) export {fishParser}