Teach fish_indent to only indent and unindent

To be used in the following commits.
This commit is contained in:
Johannes Altmanninger 2024-04-12 09:16:24 +02:00
parent 7369516871
commit 47a446ae18
6 changed files with 114 additions and 8 deletions

View File

@ -25,6 +25,12 @@ The following options are available:
**-i** or **--no-indent**
Do not indent commands; only reformat to one job per line.
**--only-indent**
Do not reformat, only indent each line.
**--only-unindent**
Do not reformat, only unindent each line.
**-c** or **--check**
Do not indent, only return 0 if the code is already indented as fish_indent would, the number of failed files otherwise. Also print the failed filenames if not reading from standard input.

View File

@ -1,6 +1,8 @@
complete -c fish_indent -s h -l help -d 'Display help and exit'
complete -c fish_indent -s v -l version -d 'Display version and exit'
complete -c fish_indent -s i -l no-indent -d 'Do not indent output, only reformat into one job per line'
complete -c fish_indent -l only-indent -d 'Do not reformat, only indent lines'
complete -c fish_indent -l only-unindent -d 'Do not reformat, only unindent lines'
complete -c fish_indent -l ansi -d 'Colorize the output using ANSI escape sequences'
complete -c fish_indent -l html -d 'Output in HTML format'
complete -c fish_indent -s w -l write -d 'Write to file'

View File

@ -36,7 +36,7 @@ use fish::highlight::{colorize, highlight_shell, HighlightRole, HighlightSpec};
use fish::libc::setlinebuf;
use fish::operation_context::OperationContext;
use fish::parse_constants::{ParseTokenType, ParseTreeFlags, SourceRange};
use fish::parse_util::parse_util_compute_indents;
use fish::parse_util::{apply_indents, parse_util_compute_indents, SPACES_PER_INDENT};
use fish::print_help::print_help;
use fish::printf;
use fish::threads;
@ -52,10 +52,6 @@ use fish::{
future_feature_flags,
};
// The number of spaces per indent isn't supposed to be configurable.
// See discussion at https://github.com/fish-shell/fish-shell/pull/6790
const SPACES_PER_INDENT: usize = 4;
/// Note: this got somewhat more complicated after introducing the new AST, because that AST no
/// longer encodes detailed lexical information (e.g. every newline). This feels more complex
/// than necessary and would probably benefit from a more layered approach where we identify
@ -777,6 +773,8 @@ fn throwing_main() -> i32 {
let mut output_type = OutputType::PlainText;
let mut output_location = L!("");
let mut do_indent = true;
let mut only_indent = false;
let mut only_unindent = false;
// File path for debug output.
let mut debug_output = None;
@ -795,6 +793,8 @@ fn throwing_main() -> i32 {
),
wopt(L!("dump-parse-tree"), woption_argument_t::no_argument, 'P'),
wopt(L!("no-indent"), woption_argument_t::no_argument, 'i'),
wopt(L!("only-indent"), woption_argument_t::no_argument, '\x04'),
wopt(L!("only-unindent"), woption_argument_t::no_argument, '\x05'),
wopt(L!("help"), woption_argument_t::no_argument, 'h'),
wopt(L!("version"), woption_argument_t::no_argument, 'v'),
wopt(L!("write"), woption_argument_t::no_argument, 'w'),
@ -830,6 +830,8 @@ fn throwing_main() -> i32 {
}
'w' => output_type = OutputType::File,
'i' => do_indent = false,
'\x04' => only_indent = true,
'\x05' => only_unindent = true,
'\x01' => output_type = OutputType::Html,
'\x02' => output_type = OutputType::Ansi,
'\x03' => output_type = OutputType::PygmentsCsv,
@ -920,7 +922,45 @@ fn throwing_main() -> i32 {
continue;
}
let output_wtext = prettify(&src, do_indent);
let output_wtext = if only_indent || only_unindent {
let indents = parse_util_compute_indents(&src);
if only_indent {
apply_indents(&src, &indents)
} else {
// Only unindent.
let mut indented_everywhere = true;
for (i, c) in src.chars().enumerate() {
if c != '\n' || i + 1 == src.len() {
continue;
}
let num_spaces = SPACES_PER_INDENT * usize::try_from(indents[i + 1]).unwrap();
if src.len() < i + 1 + num_spaces
|| !src[i + 1..].chars().take(num_spaces).all(|c| c == ' ')
{
indented_everywhere = false;
break;
}
}
if indented_everywhere {
let mut out = WString::new();
let mut i = 0;
while i < src.len() {
let c = src.as_char_slice()[i];
out.push(c);
i += 1;
if c != '\n' || i == src.len() {
continue;
}
i += SPACES_PER_INDENT * usize::try_from(indents[i]).unwrap();
}
out
} else {
src.clone()
}
}
} else {
prettify(&src, do_indent)
};
// Maybe colorize.
let mut colors = vec![];

View File

@ -798,6 +798,24 @@ pub fn parse_util_compute_indents(src: &wstr) -> Vec<i32> {
indents
}
// The number of spaces per indent isn't supposed to be configurable.
// See discussion at https://github.com/fish-shell/fish-shell/pull/6790
pub const SPACES_PER_INDENT: usize = 4;
pub fn apply_indents(src: &wstr, indents: &[i32]) -> WString {
let mut indented = WString::new();
for (i, c) in src.chars().enumerate() {
indented.push(c);
if c != '\n' || i + 1 == src.len() {
continue;
}
indented.extend(
std::iter::repeat(' ').take(SPACES_PER_INDENT * usize::try_from(indents[i]).unwrap()),
);
}
indented
}
// Visit all of our nodes. When we get a job_list or case_item_list, increment indent while
// visiting its children.
struct IndentVisitor<'a> {
@ -962,7 +980,12 @@ impl<'a> NodeVisitor<'a> for IndentVisitor<'a> {
// If this is a leaf node, apply the current indentation.
if node.category() == Category::leaf && range.length() != 0 {
self.indents[range.start()..range.end()].fill(self.indent);
let leading_spaces = self.src[..range.start()]
.chars()
.rev()
.take_while(|&c| c == ' ')
.count();
self.indents[range.start() - leading_spaces..range.end()].fill(self.indent);
self.last_leaf_end = range.end();
self.last_indent = self.indent;
}

View File

@ -85,6 +85,7 @@ use crate::pager::{PageRendering, Pager, SelectionMotion};
use crate::parse_constants::SourceRange;
use crate::parse_constants::{ParseTreeFlags, ParserTestErrorBits};
use crate::parse_tree::ParsedSource;
use crate::parse_util::SPACES_PER_INDENT;
use crate::parse_util::{
parse_util_cmdsubst_extent, parse_util_compute_indents, parse_util_contains_wildcards,
parse_util_detect_errors, parse_util_detect_errors_in_ast, parse_util_escape_string_with_quote,
@ -2789,7 +2790,9 @@ impl ReaderData {
let total_offset_new = parse_util_get_offset(
el.text(),
line_new,
line_offset_old - 4 * (indent_new - indent_old),
line_offset_old
- isize::try_from(SPACES_PER_INDENT).unwrap()
* (indent_new - indent_old),
);
self.update_buff_pos(elt, total_offset_new);
}

View File

@ -464,3 +464,35 @@ echo "
echo this file starts late
" | $fish_indent
#CHECK: echo this file starts late
echo 'foo|bar; begin
echo' | $fish_indent --only-indent
# CHECK: foo|bar; begin
# CHECK: {{^}} echo
echo 'begin
echo
end' | $fish_indent --only-unindent
# CHECK: {{^}}begin
# CHECK: {{^}}echo
# CHECK: {{^}}end
echo 'if true
begin
echo
end
end' | $fish_indent --only-unindent
# CHECK: {{^}}if true
# CHECK: {{^}}begin
# CHECK: {{^}}echo
# CHECK: {{^}}end
# CHECK: {{^}}end
echo 'begin
echo
not indented properly
end' | $fish_indent --only-unindent
# CHECK: {{^}}begin
# CHECK: {{^}} echo
# CHECK: {{^}} not indented properly
# CHECK: {{^}}end