mirror of
https://github.com/RGBCube/serenity
synced 2025-05-28 08:15:07 +00:00
Shell: Add support for heredocs
Closes #4283. Heredocs are implemented in a way that makes them feel more like a string (and not a weird redirection, a la bash). There are two tunables, whether the string is dedented (`<<-` vs `<<~`) and whether it allows interpolation (quoted key vs not). To the familiar people, this is how Ruby handles them, and I feel is the most elegant heredoc syntax. Unlike the oddjob that is bash, heredocs are treated exactly as normal strings, and can be used _anywhere_ where a string can be used. They are *required* to appear in the same order as used after a newline is seen when parsing the sequence that the heredoc is used in. For instance: ```sh echo <<-doc1 <<-doc2 | blah blah contents for doc1 doc1 contents for doc2 doc2 ``` The typical nice errors are also implemented :^)
This commit is contained in:
parent
7c8d39e002
commit
3048274f5e
7 changed files with 364 additions and 10 deletions
|
@ -1307,6 +1307,78 @@ Glob::~Glob()
|
|||
{
|
||||
}
|
||||
|
||||
void Heredoc::dump(int level) const
|
||||
{
|
||||
Node::dump(level);
|
||||
print_indented("(End Key)", level + 1);
|
||||
print_indented(m_end, level + 2);
|
||||
print_indented("(Allows Interpolation)", level + 1);
|
||||
print_indented(String::formatted("{}", m_allows_interpolation), level + 2);
|
||||
print_indented("(Contents)", level + 1);
|
||||
if (m_contents)
|
||||
m_contents->dump(level + 2);
|
||||
else
|
||||
print_indented("(null)", level + 2);
|
||||
}
|
||||
|
||||
RefPtr<Value> Heredoc::run(RefPtr<Shell> shell)
|
||||
{
|
||||
if (!m_deindent)
|
||||
return m_contents->run(shell);
|
||||
|
||||
// To deindent, first split to lines...
|
||||
auto value = m_contents->run(shell);
|
||||
if (!value)
|
||||
return value;
|
||||
auto list = value->resolve_as_list(shell);
|
||||
// The list better have one entry, otherwise we've put the wrong kind of node inside this heredoc
|
||||
VERIFY(list.size() == 1);
|
||||
auto lines = list.first().split_view('\n');
|
||||
|
||||
// Now just trim each line and put them back in a string
|
||||
StringBuilder builder { list.first().length() };
|
||||
for (auto& line : lines) {
|
||||
builder.append(line.trim_whitespace(TrimMode::Left));
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
return create<StringValue>(builder.to_string());
|
||||
}
|
||||
|
||||
void Heredoc::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
|
||||
{
|
||||
Line::Style content_style { Line::Style::Foreground(Line::Style::XtermColor::Yellow) };
|
||||
if (metadata.is_first_in_list)
|
||||
content_style.unify_with({ Line::Style::Bold });
|
||||
|
||||
if (!m_contents)
|
||||
content_style.unify_with({ Line::Style::Foreground(Line::Style::XtermColor::Red) }, true);
|
||||
|
||||
editor.stylize({ m_position.start_offset, m_position.end_offset }, content_style);
|
||||
if (m_contents)
|
||||
m_contents->highlight_in_editor(editor, shell, metadata);
|
||||
}
|
||||
|
||||
HitTestResult Heredoc::hit_test_position(size_t offset) const
|
||||
{
|
||||
if (!m_contents)
|
||||
return {};
|
||||
|
||||
return m_contents->hit_test_position(offset);
|
||||
}
|
||||
|
||||
Heredoc::Heredoc(Position position, String end, bool allow_interpolation, bool deindent)
|
||||
: Node(move(position))
|
||||
, m_end(move(end))
|
||||
, m_allows_interpolation(allow_interpolation)
|
||||
, m_deindent(deindent)
|
||||
{
|
||||
}
|
||||
|
||||
Heredoc::~Heredoc()
|
||||
{
|
||||
}
|
||||
|
||||
void HistoryEvent::dump(int level) const
|
||||
{
|
||||
Node::dump(level);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue