mirror of
https://github.com/wader/fq.git
synced 2024-11-24 03:05:22 +03:00
37d07926c1
repl: Fix completion of non-underscore extkeys
247 lines
6.5 KiB
Plaintext
247 lines
6.5 KiB
Plaintext
include "query";
|
|
|
|
# TODO: currently only make sense to allow keywords start start a term or directive
|
|
def _complete_keywords:
|
|
[
|
|
"and",
|
|
#"as",
|
|
#"break",
|
|
#"catch",
|
|
"def",
|
|
#"elif",
|
|
#"else",
|
|
#"end",
|
|
"false",
|
|
"foreach",
|
|
"if",
|
|
"import",
|
|
"include",
|
|
"label",
|
|
"module",
|
|
"null",
|
|
"or",
|
|
"reduce",
|
|
#"then",
|
|
"true",
|
|
"try"
|
|
];
|
|
|
|
def _complete_scope:
|
|
[scope[], _complete_keywords[]];
|
|
def _complete_keys:
|
|
[keys[]?, _extkeys[]?];
|
|
|
|
# TODO: handle variables via ast walk?
|
|
# TODO: refactor this
|
|
# TODO: completionMode
|
|
# TODO: return escaped identifier, not sure current readline implementation supports
|
|
# modifying "previous" characters if quoting is needed
|
|
# completions that needs to change previous input, ex: .a\t -> ."a \" b" etc
|
|
def _complete($line; $cursor_pos):
|
|
# TODO: reverse this? word or non-ident char?
|
|
def _is_separator: . as $c | " .;[]()|=" | contains($c);
|
|
def _is_internal: startswith("_") or startswith("$_");
|
|
def _query_index_or_key($q):
|
|
( ([.[] | eval($q) | type]) as $n
|
|
| if ($n | all(. == "object")) then "."
|
|
elif ($n | all(. == "array")) then "[]"
|
|
else null
|
|
end
|
|
);
|
|
# only complete if at end or there is a whitespace for now
|
|
if ($line[$cursor_pos] | . == "" or _is_separator) then
|
|
( . as $c
|
|
| $line[0:$cursor_pos]
|
|
| . as $line_query
|
|
# expr -> map(partial-expr | . | f?) | add
|
|
# TODO: move map/add logic to here?
|
|
| _query_completion(
|
|
if .type | . == "func" or . == "var" then "_complete_scope"
|
|
elif .type == "index" then "_complete_keys"
|
|
else error("unreachable")
|
|
end
|
|
) as {$type, $query, $prefix}
|
|
| {
|
|
prefix: $prefix,
|
|
names: (
|
|
if $type == "none" then
|
|
( $c
|
|
| _query_index_or_key($line_query)
|
|
| if . then [.] else [] end
|
|
)
|
|
else
|
|
( $c
|
|
| eval($query)
|
|
| ($prefix | _is_internal) as $prefix_is_internal
|
|
| map(
|
|
select(
|
|
strings and
|
|
# TODO: var type really needed? just func?
|
|
(_is_ident or $type == "var") and
|
|
((_is_internal | not) or $prefix_is_internal) and
|
|
startswith($prefix)
|
|
)
|
|
)
|
|
| unique
|
|
| sort
|
|
| if length == 1 and .[0] == $prefix then
|
|
( $c
|
|
| _query_index_or_key($line_query)
|
|
| if . then [$prefix+.] else [$prefix] end
|
|
)
|
|
end
|
|
)
|
|
end
|
|
)
|
|
}
|
|
)
|
|
else
|
|
{prefix: "", names: []}
|
|
end;
|
|
def _complete($line): _complete($line; $line | length);
|
|
|
|
# empty input []
|
|
# >* empty>
|
|
# single input [v]
|
|
# >* VALUE_PATH VALUE_PREVIEW>
|
|
# multiple inputs [v,...]
|
|
# >* VALUE_PATH VALUE_PREVIEW, ...[#]>
|
|
# single/multi inputs where first input is array [[v,...], ...]
|
|
# >* [VALUE_PATH VALUE_PREVIEW, ...][#], ...[#]>
|
|
def _prompt:
|
|
def _repl_level:
|
|
(_options_stack | length | if . > 2 then ((.-2) * ">") else empty end);
|
|
def _value_path:
|
|
(._path? // []) | if . == [] then empty else path_to_expr end;
|
|
def _value_preview($depth):
|
|
if $depth == 0 and format == null and type == "array" then
|
|
[ "["
|
|
, if length == 0 then empty
|
|
else
|
|
( (.[0] | _value_preview(1))
|
|
, if length > 1 then ", ..." else empty end
|
|
)
|
|
end
|
|
, "]"
|
|
, if length > 1 then "[0:\(length)]" else empty end
|
|
] | join("")
|
|
else
|
|
( . as $c
|
|
| format
|
|
| if . != null then
|
|
( .
|
|
+ if $c._error then "!" else "" end
|
|
)
|
|
else
|
|
($c | type)
|
|
end
|
|
)
|
|
end;
|
|
def _value:
|
|
[ _value_path
|
|
, _value_preview(0)
|
|
] | join(" ");
|
|
def _values:
|
|
if length == 0 then "empty"
|
|
else
|
|
[ (.[0] | _value)
|
|
, if length > 1 then ", ...[0:\(length)][]" else empty end
|
|
] | join("")
|
|
end;
|
|
[ _repl_level
|
|
, _values
|
|
] | join(" ") + "> ";
|
|
|
|
# _repl_display takes a opts arg to make it possible for repl_eval to
|
|
# just call options/0 once per eval even if it was multiple outputs
|
|
def _repl_display_opts: options({depth: 1});
|
|
def _repl_display($opts): display($opts);
|
|
def _repl_display: display(_repl_display_opts);
|
|
def _repl_on_error:
|
|
( if _eval_is_compile_error then _eval_compile_error_tostring
|
|
# was interrupted by user, just ignore
|
|
elif _is_context_canceled_error then empty
|
|
end
|
|
| (_error_str | println)
|
|
);
|
|
def _repl_on_compile_error: _repl_on_error;
|
|
def _repl_eval($expr):
|
|
( _repl_display_opts as $opts
|
|
| _eval(
|
|
$expr;
|
|
"repl";
|
|
_repl_display($opts);
|
|
_repl_on_error;
|
|
_repl_on_compile_error
|
|
)
|
|
);
|
|
|
|
# run read-eval-print-loop
|
|
def _repl($opts): #:: a|(Opts) => @
|
|
def _read_expr:
|
|
_repeat_break(
|
|
# both _prompt and _complete want input arrays
|
|
( _readline({prompt: _prompt, complete: "_complete", timeout: 1})
|
|
| if trim == "" then empty
|
|
else (., error("break"))
|
|
end
|
|
)
|
|
);
|
|
def _repl_loop:
|
|
( . as $c
|
|
| try
|
|
( _read_expr
|
|
| . as $expr
|
|
| try _query_fromstring
|
|
# TODO: nicer way to set filename for error message
|
|
catch (. | .filename = "repl")
|
|
| if _query_pipe_last | _query_is_func("repl") then
|
|
( _query_slurp_wrap(_query_func_rename("_repl_slurp"))
|
|
| _query_tostring as $wrap_expr
|
|
| $c
|
|
| _repl_eval($wrap_expr)
|
|
)
|
|
else
|
|
( $c
|
|
| .[]
|
|
| _repl_eval($expr)
|
|
)
|
|
end
|
|
)
|
|
catch
|
|
if . == "interrupt" then empty
|
|
elif . == "eof" then error("break")
|
|
elif _eval_is_compile_error then _repl_on_error
|
|
else error
|
|
end
|
|
);
|
|
if _is_completing | not then
|
|
( _options_stack(. + [$opts]) as $_
|
|
| _finally(
|
|
_repeat_break(_repl_loop);
|
|
_options_stack(.[:-1])
|
|
)
|
|
)
|
|
else empty
|
|
end;
|
|
|
|
def _repl_slurp($opts): _repl($opts);
|
|
def _repl_slurp: _repl({});
|
|
|
|
# TODO: introspect and show doc, reflection somehow?
|
|
def help:
|
|
( "Type expression to evaluate"
|
|
, "\\t Completion"
|
|
, "Up/Down History"
|
|
, "^C Interrupt execution"
|
|
, "... | repl Start a new REPL"
|
|
, "^D Exit REPL"
|
|
) | println;
|
|
|
|
# just gives error, call appearing last will be renamed to _repl_slurp
|
|
def repl($_):
|
|
if options.repl then error("repl must be last")
|
|
else error("repl can only be used from interactive repl")
|
|
end;
|
|
def repl: repl(null);
|