2016-04-21 06:00:54 +00:00
|
|
|
// The fish_indent program.
|
2007-04-22 10:03:12 +00:00
|
|
|
/*
|
2014-12-23 23:29:42 +00:00
|
|
|
Copyright (C) 2014 ridiculous_fish
|
2007-04-22 10:03:12 +00:00
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License version 2 as
|
|
|
|
published by the Free Software Foundation.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
2013-12-13 20:51:52 +00:00
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
2007-04-22 10:03:12 +00:00
|
|
|
*/
|
2016-05-18 22:30:21 +00:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
2016-05-18 15:37:13 +00:00
|
|
|
#include <errno.h>
|
2016-05-01 01:37:19 +00:00
|
|
|
#include <getopt.h>
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <locale.h>
|
|
|
|
#include <stddef.h>
|
2016-05-01 01:37:19 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2016-04-21 06:00:54 +00:00
|
|
|
#include <wctype.h>
|
2017-02-14 04:37:27 +00:00
|
|
|
|
2019-10-13 22:50:48 +00:00
|
|
|
#include <cstring>
|
2019-03-12 21:06:01 +00:00
|
|
|
#include <cwchar>
|
2016-05-01 01:37:19 +00:00
|
|
|
#include <memory>
|
2019-02-25 01:40:03 +00:00
|
|
|
#include <stack>
|
2019-05-05 10:09:25 +00:00
|
|
|
#include <string>
|
2019-02-25 01:40:03 +00:00
|
|
|
#include <tuple>
|
2019-05-05 10:09:25 +00:00
|
|
|
#include <vector>
|
2007-04-22 10:03:12 +00:00
|
|
|
|
2015-07-25 15:14:25 +00:00
|
|
|
#include "color.h"
|
2014-12-23 23:29:42 +00:00
|
|
|
#include "common.h"
|
|
|
|
#include "env.h"
|
2019-06-11 17:35:49 +00:00
|
|
|
#include "expand.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "fish_version.h"
|
2020-01-19 12:38:47 +00:00
|
|
|
#include "flog.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "highlight.h"
|
2020-01-16 01:14:47 +00:00
|
|
|
#include "operation_context.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "output.h"
|
|
|
|
#include "parse_constants.h"
|
2007-04-22 10:03:12 +00:00
|
|
|
#include "print_help.h"
|
2018-01-20 19:58:57 +00:00
|
|
|
#include "tnode.h"
|
2016-05-01 01:37:19 +00:00
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2007-04-22 10:03:12 +00:00
|
|
|
|
2014-12-23 23:29:42 +00:00
|
|
|
#define SPACES_PER_INDENT 4
|
2007-04-22 10:03:12 +00:00
|
|
|
|
2016-04-05 22:43:24 +00:00
|
|
|
// An indent_t represents an abstract indent depth. 2 means we are in a doubly-nested block, etc.
|
2019-11-26 00:56:39 +00:00
|
|
|
using indent_t = unsigned int;
|
2016-04-05 22:43:24 +00:00
|
|
|
static bool dump_parse_tree = false;
|
2018-12-11 13:40:57 +00:00
|
|
|
static int ret = 0;
|
2014-12-23 23:29:42 +00:00
|
|
|
|
2016-04-05 22:43:24 +00:00
|
|
|
// Read the entire contents of a file into the specified string.
|
2016-05-01 01:37:19 +00:00
|
|
|
static wcstring read_file(FILE *f) {
|
2014-12-23 23:29:42 +00:00
|
|
|
wcstring result;
|
2019-11-26 00:36:13 +00:00
|
|
|
while (true) {
|
2019-03-12 21:06:01 +00:00
|
|
|
wint_t c = std::fgetwc(f);
|
2018-12-11 13:40:57 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
if (c == WEOF) {
|
|
|
|
if (ferror(f)) {
|
2018-12-11 13:40:57 +00:00
|
|
|
if (errno == EILSEQ) {
|
|
|
|
// Illegal byte sequence. Try to skip past it.
|
|
|
|
clearerr(f);
|
2019-05-05 10:09:25 +00:00
|
|
|
int ch = fgetc(f); // for printing the warning, and seeks forward 1 byte.
|
2020-01-19 12:38:47 +00:00
|
|
|
FLOGF(warning, "%s (byte=%X)", std::strerror(errno), ch);
|
2018-12-11 13:40:57 +00:00
|
|
|
ret = 1;
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
wperror(L"fgetwc");
|
|
|
|
exit(1);
|
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2019-11-19 01:08:16 +00:00
|
|
|
result.push_back(static_cast<wchar_t>(c));
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2014-12-23 23:29:42 +00:00
|
|
|
return result;
|
2007-04-22 10:03:12 +00:00
|
|
|
}
|
|
|
|
|
2018-05-07 22:52:04 +00:00
|
|
|
struct prettifier_t {
|
|
|
|
// Original source.
|
|
|
|
const wcstring &source;
|
|
|
|
|
|
|
|
// The prettifier output.
|
|
|
|
wcstring output;
|
|
|
|
|
|
|
|
// Whether to indent, or just insert spaces.
|
|
|
|
const bool do_indent;
|
|
|
|
|
|
|
|
// Whether we are at the beginning of a new line.
|
|
|
|
bool has_new_line = true;
|
|
|
|
|
2019-05-03 17:00:55 +00:00
|
|
|
// Whether the last token was a semicolon.
|
|
|
|
bool last_was_semicolon = false;
|
|
|
|
|
2018-05-07 22:52:04 +00:00
|
|
|
// Whether we need to append a continuation new line before continuing.
|
|
|
|
bool needs_continuation_newline = false;
|
|
|
|
|
|
|
|
// Additional indentation due to line continuation (escaped newline)
|
|
|
|
uint32_t line_continuation_indent = 0;
|
|
|
|
|
|
|
|
prettifier_t(const wcstring &source, bool do_indent) : source(source), do_indent(do_indent) {}
|
|
|
|
|
2019-03-12 16:52:46 +00:00
|
|
|
void prettify_node(const parse_node_tree_t &tree, node_offset_t node_idx, indent_t node_indent,
|
|
|
|
parse_token_type_t parent_type);
|
2018-05-07 22:52:04 +00:00
|
|
|
|
|
|
|
void maybe_prepend_escaped_newline(const parse_node_t &node) {
|
|
|
|
if (node.has_preceding_escaped_newline()) {
|
|
|
|
output.append(L" \\");
|
|
|
|
append_newline(true);
|
|
|
|
}
|
2014-12-23 23:29:42 +00:00
|
|
|
}
|
2018-05-07 22:52:04 +00:00
|
|
|
|
|
|
|
void append_newline(bool is_continuation = false) {
|
|
|
|
output.push_back('\n');
|
|
|
|
has_new_line = true;
|
|
|
|
needs_continuation_newline = false;
|
|
|
|
line_continuation_indent = is_continuation ? 1 : 0;
|
|
|
|
}
|
|
|
|
|
2019-05-05 10:09:25 +00:00
|
|
|
// Append whitespace as necessary. If we have a newline, append the appropriate indent.
|
|
|
|
// Otherwise, append a space.
|
2018-05-07 22:52:04 +00:00
|
|
|
void append_whitespace(indent_t node_indent) {
|
|
|
|
if (needs_continuation_newline) {
|
|
|
|
append_newline(true);
|
|
|
|
}
|
|
|
|
if (!has_new_line) {
|
|
|
|
output.push_back(L' ');
|
|
|
|
} else if (do_indent) {
|
|
|
|
output.append((node_indent + line_continuation_indent) * SPACES_PER_INDENT, L' ');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2007-04-22 10:03:12 +00:00
|
|
|
|
2016-04-05 22:43:24 +00:00
|
|
|
// Dump a parse tree node in a form helpful to someone debugging the behavior of this program.
|
2016-05-01 01:37:19 +00:00
|
|
|
static void dump_node(indent_t node_indent, const parse_node_t &node, const wcstring &source) {
|
2016-07-16 02:45:54 +00:00
|
|
|
wchar_t nextc = L' ';
|
|
|
|
wchar_t prevc = L' ';
|
2019-11-26 00:34:06 +00:00
|
|
|
wcstring source_txt;
|
2016-07-16 02:45:54 +00:00
|
|
|
if (node.source_start != SOURCE_OFFSET_INVALID && node.source_length != SOURCE_OFFSET_INVALID) {
|
|
|
|
int nextc_idx = node.source_start + node.source_length;
|
2019-11-19 01:08:16 +00:00
|
|
|
if (static_cast<size_t>(nextc_idx) < source.size()) {
|
2016-10-09 21:43:25 +00:00
|
|
|
nextc = source[node.source_start + node.source_length];
|
|
|
|
}
|
2016-07-16 02:45:54 +00:00
|
|
|
if (node.source_start > 0) prevc = source[node.source_start - 1];
|
|
|
|
source_txt = source.substr(node.source_start, node.source_length);
|
|
|
|
}
|
2016-04-05 22:43:24 +00:00
|
|
|
wchar_t prevc_str[4] = {prevc, 0, 0, 0};
|
|
|
|
wchar_t nextc_str[4] = {nextc, 0, 0, 0};
|
2016-05-01 01:37:19 +00:00
|
|
|
if (prevc < L' ') {
|
2016-04-05 22:43:24 +00:00
|
|
|
prevc_str[0] = L'\\';
|
|
|
|
prevc_str[1] = L'c';
|
|
|
|
prevc_str[2] = prevc + '@';
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
if (nextc < L' ') {
|
2016-04-05 22:43:24 +00:00
|
|
|
nextc_str[0] = L'\\';
|
|
|
|
nextc_str[1] = L'c';
|
|
|
|
nextc_str[2] = nextc + '@';
|
|
|
|
}
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, L"{off %4u, len %4u, indent %2u, kw %ls, %ls} [%ls|%ls|%ls]\n",
|
2019-05-05 10:09:25 +00:00
|
|
|
node.source_start, node.source_length, node_indent,
|
|
|
|
keyword_description(node.keyword), token_type_description(node.type), prevc_str,
|
|
|
|
source_txt.c_str(), nextc_str);
|
2016-04-05 22:43:24 +00:00
|
|
|
}
|
|
|
|
|
2019-03-12 16:52:46 +00:00
|
|
|
void prettifier_t::prettify_node(const parse_node_tree_t &tree, node_offset_t node_idx,
|
|
|
|
indent_t node_indent, parse_token_type_t parent_type) {
|
|
|
|
// Use an explicit stack to avoid stack overflow.
|
|
|
|
struct pending_node_t {
|
|
|
|
node_offset_t index;
|
|
|
|
indent_t indent;
|
|
|
|
parse_token_type_t parent_type;
|
|
|
|
};
|
|
|
|
std::stack<pending_node_t> pending_node_stack;
|
|
|
|
|
|
|
|
pending_node_stack.push({node_idx, node_indent, parent_type});
|
|
|
|
while (!pending_node_stack.empty()) {
|
|
|
|
pending_node_t args = pending_node_stack.top();
|
|
|
|
pending_node_stack.pop();
|
|
|
|
auto node_idx = args.index;
|
|
|
|
auto node_indent = args.indent;
|
|
|
|
auto parent_type = args.parent_type;
|
2019-02-25 01:40:03 +00:00
|
|
|
|
|
|
|
const parse_node_t &node = tree.at(node_idx);
|
|
|
|
const parse_token_type_t node_type = node.type;
|
|
|
|
const parse_token_type_t prev_node_type =
|
2019-05-05 10:09:25 +00:00
|
|
|
node_idx > 0 ? tree.at(node_idx - 1).type : token_type_invalid;
|
2019-02-25 01:40:03 +00:00
|
|
|
|
2019-05-05 10:09:25 +00:00
|
|
|
// Increment the indent if we are either a root job_list, or root case_item_list, or in an
|
|
|
|
// if or while header (#1665).
|
|
|
|
const bool is_root_job_list =
|
|
|
|
node_type == symbol_job_list && parent_type != symbol_job_list;
|
2019-02-25 01:40:03 +00:00
|
|
|
const bool is_root_case_list =
|
2019-05-05 10:09:25 +00:00
|
|
|
node_type == symbol_case_item_list && parent_type != symbol_case_item_list;
|
2019-02-25 01:40:03 +00:00
|
|
|
const bool is_if_while_header =
|
2019-05-05 10:09:25 +00:00
|
|
|
(node_type == symbol_job_conjunction || node_type == symbol_andor_job_list) &&
|
|
|
|
(parent_type == symbol_if_clause || parent_type == symbol_while_header);
|
2019-02-25 01:40:03 +00:00
|
|
|
|
|
|
|
if (is_root_job_list || is_root_case_list || is_if_while_header) {
|
|
|
|
node_indent += 1;
|
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2019-02-25 01:40:03 +00:00
|
|
|
if (dump_parse_tree) dump_node(node_indent, node, source);
|
2016-04-05 22:43:24 +00:00
|
|
|
|
2020-03-15 14:25:34 +00:00
|
|
|
// Prepend any escaped newline, but only for certain cases.
|
2020-03-26 19:45:40 +00:00
|
|
|
// We allow it to split arguments (including at the end - this is like trailing commas in
|
|
|
|
// lists, makes for better diffs), to separate pipelines (but it has to be *before* the
|
|
|
|
// pipe, so the pipe symbol is the first thing on the new line after the indent) and to
|
|
|
|
// separate &&/|| job lists (`and` and `or` are handled separately below, as they *allow*
|
|
|
|
// semicolons)
|
2020-03-15 14:25:34 +00:00
|
|
|
// TODO: Handle
|
|
|
|
// foo | \
|
|
|
|
// bar
|
2020-03-26 19:45:40 +00:00
|
|
|
// so it just removes the escape - pipes don't need it. This was changed in some fish
|
|
|
|
// version, figure out which it was and if it is worth supporting.
|
|
|
|
if (prev_node_type == symbol_arguments_or_redirections_list ||
|
|
|
|
prev_node_type == symbol_argument_list || node_type == parse_token_type_andand ||
|
|
|
|
node_type == parse_token_type_pipe || node_type == parse_token_type_end) {
|
2020-03-15 14:25:34 +00:00
|
|
|
maybe_prepend_escaped_newline(node);
|
|
|
|
}
|
2018-05-07 22:52:04 +00:00
|
|
|
|
2019-02-25 01:40:03 +00:00
|
|
|
// handle comments, which come before the text
|
|
|
|
if (node.has_comments()) {
|
|
|
|
auto comment_nodes = tree.comment_nodes_for_node(node);
|
|
|
|
for (const auto &comment : comment_nodes) {
|
|
|
|
maybe_prepend_escaped_newline(*comment.node());
|
|
|
|
append_whitespace(node_indent);
|
|
|
|
auto source_range = comment.source_range();
|
|
|
|
output.append(source, source_range->start, source_range->length);
|
|
|
|
needs_continuation_newline = true;
|
|
|
|
}
|
2014-12-23 23:29:42 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2019-02-25 01:40:03 +00:00
|
|
|
if (node_type == parse_token_type_end) {
|
2019-05-03 17:00:55 +00:00
|
|
|
// For historical reasons, semicolon also get "TOK_END".
|
|
|
|
// We need to distinguish between them, because otherwise `a;;;;` gets extra lines
|
|
|
|
// instead of the semicolons. Semicolons are just ignored, unless they are followed by a
|
|
|
|
// command. So `echo;` removes the semicolon, but `echo; echo` removes it and adds a
|
|
|
|
// newline.
|
2019-05-05 10:51:49 +00:00
|
|
|
last_was_semicolon = false;
|
2019-05-03 17:00:55 +00:00
|
|
|
if (node.get_source(source) == L"\n") {
|
|
|
|
append_newline();
|
2019-05-05 10:51:49 +00:00
|
|
|
} else if (!has_new_line) {
|
|
|
|
// The semicolon is only useful if we haven't just had a newline.
|
2019-05-03 17:00:55 +00:00
|
|
|
last_was_semicolon = true;
|
|
|
|
}
|
2019-02-25 01:40:03 +00:00
|
|
|
} else if ((node_type >= FIRST_PARSE_TOKEN_TYPE && node_type <= LAST_PARSE_TOKEN_TYPE) ||
|
|
|
|
node_type == parse_special_type_parse_error) {
|
2019-05-03 17:00:55 +00:00
|
|
|
if (last_was_semicolon) {
|
2019-05-05 10:51:49 +00:00
|
|
|
// We keep the semicolon for `; and` and `; or`,
|
|
|
|
// others we turn into newlines.
|
2019-05-28 02:47:13 +00:00
|
|
|
if (node.keyword != parse_keyword_and && node.keyword != parse_keyword_or) {
|
2019-05-05 10:51:49 +00:00
|
|
|
append_newline();
|
|
|
|
} else {
|
|
|
|
output.push_back(L';');
|
|
|
|
}
|
2019-05-03 17:00:55 +00:00
|
|
|
last_was_semicolon = false;
|
|
|
|
}
|
2019-05-05 10:51:49 +00:00
|
|
|
|
2019-05-20 03:56:28 +00:00
|
|
|
if (node.has_source()) {
|
2019-02-25 01:40:03 +00:00
|
|
|
// Some type representing a particular token.
|
|
|
|
if (prev_node_type != parse_token_type_redirection) {
|
|
|
|
append_whitespace(node_indent);
|
|
|
|
}
|
2019-06-11 17:35:49 +00:00
|
|
|
wcstring unescaped{source, node.source_start, node.source_length};
|
2020-03-08 13:03:40 +00:00
|
|
|
// Unescape the string - this leaves special markers around if there are any
|
|
|
|
// expansions or anything. We specifically tell it to not compute backslash-escapes
|
|
|
|
// like \U or \x, because we want to leave them intact.
|
|
|
|
unescape_string_in_place(&unescaped, UNESCAPE_SPECIAL | UNESCAPE_NO_BACKSLASHES);
|
2019-06-11 17:35:49 +00:00
|
|
|
|
|
|
|
// Remove INTERNAL_SEPARATOR because that's a quote.
|
|
|
|
auto quote = [](wchar_t ch) { return ch == INTERNAL_SEPARATOR; };
|
|
|
|
unescaped.erase(std::remove_if(unescaped.begin(), unescaped.end(), quote),
|
|
|
|
unescaped.end());
|
|
|
|
|
2020-03-08 19:25:12 +00:00
|
|
|
// If no non-"good" char is left, use the unescaped version.
|
2019-06-11 17:35:49 +00:00
|
|
|
// This can be extended to other characters, but giving the precise list is tough,
|
2020-03-08 19:25:12 +00:00
|
|
|
// can change over time (see "^", "%" and "?", in some cases "{}") and it just makes
|
|
|
|
// people feel more at ease.
|
2020-03-26 19:45:40 +00:00
|
|
|
auto goodchars = [](wchar_t ch) {
|
|
|
|
return fish_iswalnum(ch) || ch == L'_' || ch == L'-' || ch == L'/';
|
|
|
|
};
|
2020-03-08 19:25:12 +00:00
|
|
|
if (std::find_if_not(unescaped.begin(), unescaped.end(), goodchars) ==
|
|
|
|
unescaped.end() &&
|
|
|
|
!unescaped.empty()) {
|
2019-06-11 17:35:49 +00:00
|
|
|
output.append(unescaped);
|
|
|
|
} else {
|
|
|
|
output.append(source, node.source_start, node.source_length);
|
|
|
|
}
|
2019-02-25 01:40:03 +00:00
|
|
|
has_new_line = false;
|
2016-04-05 22:43:24 +00:00
|
|
|
}
|
2014-12-23 23:29:42 +00:00
|
|
|
}
|
2012-11-19 08:31:03 +00:00
|
|
|
|
2019-02-25 01:40:03 +00:00
|
|
|
// Put all children in stack in reversed order
|
|
|
|
// This way they will be processed in correct order.
|
|
|
|
for (node_offset_t idx = node.child_count; idx > 0; idx--) {
|
|
|
|
// Note: We pass our type to our child, which becomes its parent node type.
|
|
|
|
// Note: While node.child_start could be -1 (NODE_OFFSET_INVALID) the addition is safe
|
2019-05-05 10:09:25 +00:00
|
|
|
// because we won't execute this call in that case since node.child_count should be
|
|
|
|
// zero.
|
2019-03-12 16:52:46 +00:00
|
|
|
pending_node_stack.push({node.child_start + (idx - 1), node_indent, node_type});
|
2019-02-25 01:40:03 +00:00
|
|
|
}
|
2014-12-23 23:29:42 +00:00
|
|
|
}
|
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2019-03-13 01:05:56 +00:00
|
|
|
static const char *highlight_role_to_string(highlight_role_t role) {
|
|
|
|
#define TEST_ROLE(x) \
|
|
|
|
case highlight_role_t::x: \
|
|
|
|
return #x;
|
|
|
|
switch (role) {
|
|
|
|
TEST_ROLE(normal)
|
|
|
|
TEST_ROLE(error)
|
|
|
|
TEST_ROLE(command)
|
|
|
|
TEST_ROLE(statement_terminator)
|
|
|
|
TEST_ROLE(param)
|
|
|
|
TEST_ROLE(comment)
|
|
|
|
TEST_ROLE(match)
|
|
|
|
TEST_ROLE(search_match)
|
|
|
|
TEST_ROLE(operat)
|
|
|
|
TEST_ROLE(escape)
|
|
|
|
TEST_ROLE(quote)
|
|
|
|
TEST_ROLE(redirection)
|
|
|
|
TEST_ROLE(autosuggestion)
|
|
|
|
TEST_ROLE(selection)
|
|
|
|
TEST_ROLE(pager_progress)
|
|
|
|
TEST_ROLE(pager_background)
|
|
|
|
TEST_ROLE(pager_prefix)
|
|
|
|
TEST_ROLE(pager_completion)
|
|
|
|
TEST_ROLE(pager_description)
|
|
|
|
TEST_ROLE(pager_secondary_background)
|
|
|
|
TEST_ROLE(pager_secondary_prefix)
|
|
|
|
TEST_ROLE(pager_secondary_completion)
|
|
|
|
TEST_ROLE(pager_secondary_description)
|
|
|
|
TEST_ROLE(pager_selected_background)
|
|
|
|
TEST_ROLE(pager_selected_prefix)
|
|
|
|
TEST_ROLE(pager_selected_completion)
|
|
|
|
TEST_ROLE(pager_selected_description)
|
2019-04-12 13:38:38 +00:00
|
|
|
default:
|
|
|
|
DIE("UNKNOWN ROLE");
|
2019-03-13 01:05:56 +00:00
|
|
|
}
|
|
|
|
#undef TEST_ROLE
|
|
|
|
}
|
|
|
|
|
|
|
|
// Entry point for Pygments CSV output.
|
|
|
|
// Our output is a newline-separated string.
|
|
|
|
// Each line is of the form `start,end,role`
|
|
|
|
// start and end is the half-open token range, value is a string from highlight_role_t.
|
|
|
|
// Example:
|
|
|
|
// 3,7,command
|
|
|
|
static std::string make_pygments_csv(const wcstring &src) {
|
|
|
|
const size_t len = src.size();
|
|
|
|
std::vector<highlight_spec_t> colors;
|
2020-01-16 01:14:47 +00:00
|
|
|
highlight_shell_no_io(src, colors, src.size(), operation_context_t::globals());
|
2019-03-13 01:05:56 +00:00
|
|
|
assert(colors.size() == len && "Colors and src should have same size");
|
|
|
|
|
|
|
|
struct token_range_t {
|
|
|
|
unsigned long start;
|
|
|
|
unsigned long end;
|
|
|
|
highlight_role_t role;
|
|
|
|
};
|
|
|
|
|
|
|
|
std::vector<token_range_t> token_ranges;
|
|
|
|
for (size_t i = 0; i < len; i++) {
|
|
|
|
highlight_role_t role = colors.at(i).foreground;
|
|
|
|
// See if we can extend the last range.
|
|
|
|
if (!token_ranges.empty()) {
|
|
|
|
auto &last = token_ranges.back();
|
|
|
|
if (last.role == role && last.end == i) {
|
|
|
|
last.end = i + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// We need a new range.
|
|
|
|
token_ranges.push_back(token_range_t{i, i + 1, role});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now render these to a string.
|
|
|
|
std::string result;
|
|
|
|
for (const auto &range : token_ranges) {
|
|
|
|
char buff[128];
|
|
|
|
snprintf(buff, sizeof buff, "%lu,%lu,%s\n", range.start, range.end,
|
|
|
|
highlight_role_to_string(range.role));
|
|
|
|
result.append(buff);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Entry point for prettification.
|
|
|
|
static wcstring prettify(const wcstring &src, bool do_indent) {
|
2017-06-13 00:59:44 +00:00
|
|
|
parse_node_tree_t parse_tree;
|
2016-05-18 15:37:13 +00:00
|
|
|
int parse_flags = (parse_flag_continue_after_error | parse_flag_include_comments |
|
|
|
|
parse_flag_leave_unterminated | parse_flag_show_blank_lines);
|
2019-11-19 02:34:50 +00:00
|
|
|
if (!parse_tree_from_string(src, parse_flags, &parse_tree, nullptr)) {
|
2017-06-13 00:59:44 +00:00
|
|
|
return src; // we return the original string on failure
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dump_parse_tree) {
|
|
|
|
const wcstring dump = parse_dump_tree(parse_tree, src);
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, L"%ls\n", dump.c_str());
|
2014-12-23 23:29:42 +00:00
|
|
|
}
|
2012-11-19 08:31:03 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// We may have a forest of disconnected trees on a parse failure. We have to handle all nodes
|
|
|
|
// that have no parent, and all parse errors.
|
2018-05-07 22:52:04 +00:00
|
|
|
prettifier_t prettifier{src, do_indent};
|
2017-06-13 00:59:44 +00:00
|
|
|
for (node_offset_t i = 0; i < parse_tree.size(); i++) {
|
|
|
|
const parse_node_t &node = parse_tree.at(i);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (node.parent == NODE_OFFSET_INVALID || node.type == parse_special_type_parse_error) {
|
|
|
|
// A root node.
|
2019-03-12 16:52:46 +00:00
|
|
|
prettifier.prettify_node(parse_tree, i, 0, symbol_job_list);
|
2014-12-23 23:29:42 +00:00
|
|
|
}
|
|
|
|
}
|
2018-05-07 22:52:04 +00:00
|
|
|
return std::move(prettifier.output);
|
2014-12-23 23:29:42 +00:00
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
/// Given a string and list of colors of the same size, return the string with HTML span elements
|
|
|
|
/// for the various colors.
|
|
|
|
static const wchar_t *html_class_name_for_color(highlight_spec_t spec) {
|
|
|
|
#define P(x) L"fish_color_" #x
|
2019-03-04 01:34:00 +00:00
|
|
|
switch (spec.foreground) {
|
|
|
|
case highlight_role_t::normal: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(normal);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::error: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(error);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::command: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(command);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::statement_terminator: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(statement_terminator);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::param: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(param);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::comment: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(comment);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::match: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(match);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::search_match: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(search_match);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::operat: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(operator);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::escape: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(escape);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::quote: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(quote);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::redirection: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(redirection);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::autosuggestion: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(autosuggestion);
|
|
|
|
}
|
2019-03-04 01:34:00 +00:00
|
|
|
case highlight_role_t::selection: {
|
2016-05-01 01:37:19 +00:00
|
|
|
return P(selection);
|
|
|
|
}
|
2019-05-05 10:09:25 +00:00
|
|
|
default: {
|
|
|
|
return P(other);
|
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2007-04-22 10:03:12 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static std::string html_colorize(const wcstring &text,
|
|
|
|
const std::vector<highlight_spec_t> &colors) {
|
|
|
|
if (text.empty()) {
|
2014-12-23 23:29:42 +00:00
|
|
|
return "";
|
|
|
|
}
|
2007-04-22 18:54:29 +00:00
|
|
|
|
2014-12-23 23:29:42 +00:00
|
|
|
assert(colors.size() == text.size());
|
2014-12-24 21:08:16 +00:00
|
|
|
wcstring html = L"<pre><code>";
|
2019-03-04 01:34:00 +00:00
|
|
|
highlight_spec_t last_color = highlight_role_t::normal;
|
2016-05-01 01:37:19 +00:00
|
|
|
for (size_t i = 0; i < text.size(); i++) {
|
|
|
|
// Handle colors.
|
2014-12-23 23:29:42 +00:00
|
|
|
highlight_spec_t color = colors.at(i);
|
2016-05-01 01:37:19 +00:00
|
|
|
if (i > 0 && color != last_color) {
|
2014-12-23 23:29:42 +00:00
|
|
|
html.append(L"</span>");
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
if (i == 0 || color != last_color) {
|
2014-12-23 23:29:42 +00:00
|
|
|
append_format(html, L"<span class=\"%ls\">", html_class_name_for_color(color));
|
|
|
|
}
|
|
|
|
last_color = color;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Handle text.
|
2014-12-23 23:29:42 +00:00
|
|
|
wchar_t wc = text.at(i);
|
2016-05-01 01:37:19 +00:00
|
|
|
switch (wc) {
|
|
|
|
case L'&': {
|
2014-12-23 23:29:42 +00:00
|
|
|
html.append(L"&");
|
|
|
|
break;
|
2016-05-01 01:37:19 +00:00
|
|
|
}
|
|
|
|
case L'\'': {
|
2014-12-23 23:29:42 +00:00
|
|
|
html.append(L"'");
|
|
|
|
break;
|
2016-05-01 01:37:19 +00:00
|
|
|
}
|
|
|
|
case L'"': {
|
2014-12-23 23:29:42 +00:00
|
|
|
html.append(L""");
|
|
|
|
break;
|
2016-05-01 01:37:19 +00:00
|
|
|
}
|
|
|
|
case L'<': {
|
2014-12-23 23:29:42 +00:00
|
|
|
html.append(L"<");
|
|
|
|
break;
|
2016-05-01 01:37:19 +00:00
|
|
|
}
|
|
|
|
case L'>': {
|
2014-12-23 23:29:42 +00:00
|
|
|
html.append(L">");
|
|
|
|
break;
|
2016-05-01 01:37:19 +00:00
|
|
|
}
|
|
|
|
default: {
|
2014-12-23 23:29:42 +00:00
|
|
|
html.push_back(wc);
|
|
|
|
break;
|
2016-05-01 01:37:19 +00:00
|
|
|
}
|
2014-12-23 23:29:42 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-24 22:17:06 +00:00
|
|
|
html.append(L"</span></code></pre>");
|
2014-12-23 23:29:42 +00:00
|
|
|
return wcs2string(html);
|
2007-04-22 18:54:29 +00:00
|
|
|
}
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
static std::string no_colorize(const wcstring &text) { return wcs2string(text); }
|
2007-04-22 18:54:29 +00:00
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
int main(int argc, char *argv[]) {
|
2018-11-28 14:08:24 +00:00
|
|
|
program_name = L"fish_indent";
|
2012-11-19 00:30:30 +00:00
|
|
|
set_main_thread();
|
2012-03-07 19:35:22 +00:00
|
|
|
setup_fork_guards();
|
2016-06-04 02:05:13 +00:00
|
|
|
// Using the user's default locale could be a problem if it doesn't use UTF-8 encoding. That's
|
|
|
|
// because the fish project assumes Unicode UTF-8 encoding in all of its scripts.
|
|
|
|
//
|
|
|
|
// TODO: Auto-detect the encoding of the script. We should look for a vim style comment
|
|
|
|
// (e.g., "# vim: set fileencoding=<encoding-name>:") or an emacs style comment
|
|
|
|
// (e.g., "# -*- coding: <encoding-name> -*-").
|
|
|
|
setlocale(LC_ALL, "");
|
2014-12-23 23:29:42 +00:00
|
|
|
env_init();
|
|
|
|
|
2016-05-01 01:37:19 +00:00
|
|
|
// Types of output we support.
|
|
|
|
enum {
|
2014-12-23 23:29:42 +00:00
|
|
|
output_type_plain_text,
|
2016-05-18 15:37:13 +00:00
|
|
|
output_type_file,
|
2014-12-23 23:29:42 +00:00
|
|
|
output_type_ansi,
|
2019-03-13 01:05:56 +00:00
|
|
|
output_type_pygments_csv,
|
2014-12-23 23:29:42 +00:00
|
|
|
output_type_html
|
|
|
|
} output_type = output_type_plain_text;
|
2016-06-10 14:39:05 +00:00
|
|
|
const char *output_location = "";
|
2014-12-23 23:29:42 +00:00
|
|
|
bool do_indent = true;
|
|
|
|
|
2016-07-06 03:22:44 +00:00
|
|
|
const char *short_opts = "+d:hvwiD:";
|
2019-11-19 02:34:50 +00:00
|
|
|
const struct option long_opts[] = {{"debug-level", required_argument, nullptr, 'd'},
|
|
|
|
{"debug-stack-frames", required_argument, nullptr, 'D'},
|
|
|
|
{"dump-parse-tree", no_argument, nullptr, 'P'},
|
|
|
|
{"no-indent", no_argument, nullptr, 'i'},
|
|
|
|
{"help", no_argument, nullptr, 'h'},
|
|
|
|
{"version", no_argument, nullptr, 'v'},
|
|
|
|
{"write", no_argument, nullptr, 'w'},
|
|
|
|
{"html", no_argument, nullptr, 1},
|
|
|
|
{"ansi", no_argument, nullptr, 2},
|
|
|
|
{"pygments", no_argument, nullptr, 3},
|
|
|
|
{nullptr, 0, nullptr, 0}};
|
2016-03-03 23:36:17 +00:00
|
|
|
|
|
|
|
int opt;
|
2019-11-19 02:34:50 +00:00
|
|
|
while ((opt = getopt_long(argc, argv, short_opts, long_opts, nullptr)) != -1) {
|
2016-05-01 01:37:19 +00:00
|
|
|
switch (opt) {
|
2016-07-06 03:22:44 +00:00
|
|
|
case 'P': {
|
2016-04-05 22:43:24 +00:00
|
|
|
dump_parse_tree = true;
|
|
|
|
break;
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
case 'h': {
|
2012-11-19 08:31:03 +00:00
|
|
|
print_help("fish_indent", 1);
|
2016-05-18 15:37:13 +00:00
|
|
|
exit(0);
|
|
|
|
break;
|
2012-11-19 08:31:03 +00:00
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
case 'v': {
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, _(L"%ls, version %s\n"), program_name, get_fish_version());
|
2012-11-19 08:31:03 +00:00
|
|
|
exit(0);
|
2016-05-18 15:37:13 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'w': {
|
|
|
|
output_type = output_type_file;
|
2014-10-31 05:40:35 +00:00
|
|
|
break;
|
2012-11-19 08:31:03 +00:00
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
case 'i': {
|
2014-12-23 23:29:42 +00:00
|
|
|
do_indent = false;
|
2012-11-19 08:31:03 +00:00
|
|
|
break;
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
case 1: {
|
2014-12-23 23:29:42 +00:00
|
|
|
output_type = output_type_html;
|
|
|
|
break;
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
case 2: {
|
2014-12-23 23:29:42 +00:00
|
|
|
output_type = output_type_ansi;
|
|
|
|
break;
|
|
|
|
}
|
2019-03-13 01:05:56 +00:00
|
|
|
case 3: {
|
|
|
|
output_type = output_type_pygments_csv;
|
|
|
|
break;
|
|
|
|
}
|
2016-07-06 03:22:44 +00:00
|
|
|
case 'd': {
|
|
|
|
char *end;
|
|
|
|
long tmp;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
tmp = strtol(optarg, &end, 10);
|
|
|
|
|
|
|
|
if (tmp >= 0 && tmp <= 10 && !*end && !errno) {
|
2019-11-19 01:08:16 +00:00
|
|
|
debug_level = static_cast<int>(tmp);
|
2016-07-06 03:22:44 +00:00
|
|
|
} else {
|
2019-03-12 21:06:01 +00:00
|
|
|
std::fwprintf(stderr, _(L"Invalid value '%s' for debug-level flag"), optarg);
|
2016-07-06 03:22:44 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'D': {
|
|
|
|
char *end;
|
|
|
|
long tmp;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
tmp = strtol(optarg, &end, 10);
|
|
|
|
|
|
|
|
if (tmp > 0 && tmp <= 128 && !*end && !errno) {
|
2019-11-19 01:08:16 +00:00
|
|
|
set_debug_stack_frames(static_cast<int>(tmp));
|
2016-07-06 03:22:44 +00:00
|
|
|
} else {
|
2019-05-05 10:09:25 +00:00
|
|
|
std::fwprintf(stderr, _(L"Invalid value '%s' for debug-stack-frames flag"),
|
|
|
|
optarg);
|
2016-07-06 03:22:44 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-05-01 01:37:19 +00:00
|
|
|
default: {
|
2016-03-03 23:36:17 +00:00
|
|
|
// We assume getopt_long() has already emitted a diagnostic msg.
|
2016-05-18 15:37:13 +00:00
|
|
|
exit(1);
|
|
|
|
break;
|
2012-11-19 08:31:03 +00:00
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2007-04-22 10:03:12 +00:00
|
|
|
|
2016-05-18 15:37:13 +00:00
|
|
|
argc -= optind;
|
|
|
|
argv += optind;
|
|
|
|
|
|
|
|
wcstring src;
|
2019-05-28 02:47:13 +00:00
|
|
|
for (int i = 0; i < argc || (argc == 0 && i == 0); i++) {
|
2019-05-22 20:20:56 +00:00
|
|
|
if (argc == 0 && i == 0) {
|
|
|
|
if (output_type == output_type_file) {
|
2019-05-28 02:47:13 +00:00
|
|
|
std::fwprintf(
|
|
|
|
stderr, _(L"Expected file path to read/write for -w:\n\n $ %ls -w foo.fish\n"),
|
|
|
|
program_name);
|
2019-05-22 20:20:56 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
src = read_file(stdin);
|
|
|
|
} else {
|
2019-05-20 19:04:51 +00:00
|
|
|
FILE *fh = fopen(argv[i], "r");
|
2016-05-18 15:37:13 +00:00
|
|
|
if (fh) {
|
2019-05-20 19:04:51 +00:00
|
|
|
src = read_file(fh);
|
2016-05-18 15:37:13 +00:00
|
|
|
fclose(fh);
|
2019-05-20 19:04:51 +00:00
|
|
|
output_location = argv[i];
|
2016-05-18 15:37:13 +00:00
|
|
|
} else {
|
2019-05-28 02:47:13 +00:00
|
|
|
std::fwprintf(stderr, _(L"Opening \"%s\" failed: %s\n"), *argv,
|
|
|
|
std::strerror(errno));
|
2016-05-18 15:37:13 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2019-05-22 20:20:56 +00:00
|
|
|
}
|
2019-05-20 19:04:51 +00:00
|
|
|
|
2019-05-22 20:20:56 +00:00
|
|
|
if (output_type == output_type_pygments_csv) {
|
|
|
|
std::string output = make_pygments_csv(src);
|
|
|
|
fputs(output.c_str(), stdout);
|
2019-05-25 09:38:05 +00:00
|
|
|
continue;
|
2019-05-22 20:20:56 +00:00
|
|
|
}
|
2019-05-20 19:04:51 +00:00
|
|
|
|
2019-05-22 20:20:56 +00:00
|
|
|
const wcstring output_wtext = prettify(src, do_indent);
|
2019-05-20 19:04:51 +00:00
|
|
|
|
2019-05-22 20:20:56 +00:00
|
|
|
// Maybe colorize.
|
|
|
|
std::vector<highlight_spec_t> colors;
|
|
|
|
if (output_type != output_type_plain_text) {
|
2020-01-16 01:14:47 +00:00
|
|
|
highlight_shell_no_io(output_wtext, colors, output_wtext.size(),
|
|
|
|
operation_context_t::globals());
|
2019-05-22 20:20:56 +00:00
|
|
|
}
|
2019-05-20 19:04:51 +00:00
|
|
|
|
2019-05-22 20:20:56 +00:00
|
|
|
std::string colored_output;
|
|
|
|
switch (output_type) {
|
2019-05-28 02:47:13 +00:00
|
|
|
case output_type_plain_text: {
|
|
|
|
colored_output = no_colorize(output_wtext);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case output_type_file: {
|
|
|
|
FILE *fh = fopen(output_location, "w");
|
|
|
|
if (fh) {
|
|
|
|
std::fputws(output_wtext.c_str(), fh);
|
|
|
|
fclose(fh);
|
|
|
|
} else {
|
|
|
|
std::fwprintf(stderr, _(L"Opening \"%s\" failed: %s\n"), output_location,
|
|
|
|
std::strerror(errno));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case output_type_ansi: {
|
2019-09-19 11:27:33 +00:00
|
|
|
colored_output = colorize(output_wtext, colors);
|
2019-05-28 02:47:13 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case output_type_html: {
|
|
|
|
colored_output = html_colorize(output_wtext, colors);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case output_type_pygments_csv: {
|
|
|
|
DIE("pygments_csv should have been handled above");
|
|
|
|
break;
|
2019-05-20 19:04:51 +00:00
|
|
|
}
|
2019-05-22 20:20:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::fputws(str2wcstring(colored_output).c_str(), stdout);
|
2014-12-23 23:29:42 +00:00
|
|
|
}
|
2019-05-20 19:04:51 +00:00
|
|
|
return 0;
|
2007-04-22 10:03:12 +00:00
|
|
|
}
|