547 lines
22 KiB
C++
547 lines
22 KiB
C++
/*
|
|
4coder_auto_indent.cpp - Commands for auto-indentation of C++ code.
|
|
*/
|
|
|
|
// TOP
|
|
|
|
internal Batch_Edit*
|
|
make_batch_from_indent_marks(Application_Links *app, Arena *arena, Buffer_ID buffer,
|
|
i64 first_line, i64 one_past_last_line, i64 *indent_marks,
|
|
Indent_Options opts){
|
|
i64 *shifted_indent_marks = indent_marks - first_line;
|
|
|
|
Batch_Edit *batch_first = 0;
|
|
Batch_Edit *batch_last = 0;
|
|
|
|
for (i64 line_number = first_line;
|
|
line_number < one_past_last_line;
|
|
++line_number){
|
|
i64 line_start_pos = get_line_start_pos(app, buffer, line_number);
|
|
Indent_Info hard_start = get_indent_info_line_start(app, buffer, line_start_pos, opts.tab_width);
|
|
|
|
i64 correct_indentation = shifted_indent_marks[line_number];
|
|
if (hard_start.is_blank && opts.empty_blank_lines){
|
|
correct_indentation = 0;
|
|
}
|
|
if (correct_indentation == -1){
|
|
correct_indentation = hard_start.indent_pos;
|
|
}
|
|
|
|
if (correct_indentation != hard_start.indent_pos){
|
|
umem str_size = 0;
|
|
u8 *str = 0;
|
|
if (opts.use_tabs){
|
|
i64 tab_count = correct_indentation/opts.tab_width;
|
|
i64 indent = tab_count*opts.tab_width;
|
|
i64 space_count = correct_indentation - indent;
|
|
str_size = tab_count + space_count;
|
|
str = push_array(arena, u8, str_size);
|
|
block_fill_u8(str, tab_count, '\t');
|
|
block_fill_u8(str + tab_count, space_count, ' ');
|
|
}
|
|
else{
|
|
str_size = correct_indentation;
|
|
str = push_array(arena, u8, str_size);
|
|
block_fill_u8(str, str_size, ' ');
|
|
}
|
|
|
|
Batch_Edit *batch = push_array(arena, Batch_Edit, 1);
|
|
sll_queue_push(batch_first, batch_last, batch);
|
|
batch->edit.text = SCu8(str, str_size);
|
|
batch->edit.range = Ii64(line_start_pos, hard_start.first_char_pos);
|
|
}
|
|
}
|
|
|
|
return(batch_first);
|
|
}
|
|
|
|
internal void
|
|
set_line_indents(Application_Links *app, Arena *arena, Buffer_ID buffer, i64 first_line, i64 one_past_last_line, i64 *indent_marks, Indent_Options opts){
|
|
Batch_Edit *batch = make_batch_from_indent_marks(app, arena, buffer, first_line, one_past_last_line, indent_marks, opts);
|
|
if (batch != 0){
|
|
buffer_batch_edit(app, buffer, batch);
|
|
}
|
|
}
|
|
|
|
internal Token*
|
|
seek_matching_token_backwards(Token_Array tokens, Token *token,
|
|
Token_Base_Kind open, Token_Base_Kind close){
|
|
if (token <= tokens.tokens){
|
|
token = tokens.tokens;
|
|
}
|
|
else{
|
|
i32 nesting_level = 0;
|
|
for (; token > tokens.tokens; --token){
|
|
if (!HasFlag(token->flags, TokenBaseFlag_PreprocessorBody)){
|
|
if (token->kind == close){
|
|
++nesting_level;
|
|
}
|
|
else if (token->kind == open){
|
|
if (nesting_level == 0){
|
|
break;
|
|
}
|
|
else{
|
|
--nesting_level;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return(token);
|
|
}
|
|
|
|
internal Indent_Anchor_Position
|
|
find_anchor_token(Application_Links *app, Buffer_ID buffer, Token_Array tokens, i64 line_start, i32 tab_width){
|
|
Indent_Anchor_Position anchor = {};
|
|
if (tokens.count > 0){
|
|
Token *first_invalid_token = get_first_token_from_line(app, buffer, tokens, line_start);
|
|
if (first_invalid_token <= tokens.tokens){
|
|
anchor.token = tokens.tokens;
|
|
}
|
|
else{
|
|
i32 stack[256];
|
|
i32 top = -1;
|
|
Token *token_it = tokens.tokens;
|
|
i64 highest_checked_line_number = -1;
|
|
for (; token_it < first_invalid_token; ++token_it){
|
|
i64 line_number = get_line_number_from_pos(app, buffer, token_it->pos);
|
|
if (highest_checked_line_number < line_number){
|
|
highest_checked_line_number = line_number;
|
|
if (top == -1){
|
|
anchor.token = token_it;
|
|
}
|
|
}
|
|
|
|
switch (token_it->kind){
|
|
case TokenBaseKind_ParentheticalOpen:
|
|
case TokenBaseKind_ScopeOpen:
|
|
{
|
|
top += 1;
|
|
stack[top] = token_it->kind;
|
|
}break;
|
|
|
|
case TokenBaseKind_ParentheticalClose:
|
|
{
|
|
for (;top >= 0;){
|
|
i32 index = top;
|
|
top -= 1;
|
|
if (stack[index] == TokenBaseKind_ParentheticalOpen){
|
|
break;
|
|
}
|
|
}
|
|
}break;
|
|
|
|
case TokenBaseKind_ScopeClose:
|
|
{
|
|
for (;top >= 0;){
|
|
i32 index = top;
|
|
if (stack[index] == TokenBaseKind_ParentheticalOpen){
|
|
break;
|
|
}
|
|
top -= 1;
|
|
if (stack[index] == TokenBaseKind_ScopeClose){
|
|
break;
|
|
}
|
|
}
|
|
}break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return(anchor);
|
|
}
|
|
|
|
internal i64*
|
|
get_indentation_marks(Application_Links *app, Arena *arena, Buffer_ID buffer,
|
|
Token_Array tokens, i64 first_line, i64 one_past_last_line,
|
|
b32 exact_align, i32 tab_width){
|
|
i64 indent_mark_count = one_past_last_line - first_line;
|
|
i64 *indent_marks = push_array(arena, i64, indent_mark_count);
|
|
// Shift the array so line_index works correctly.
|
|
indent_marks -= first_line;
|
|
|
|
// Decide where to start indentation parsing.
|
|
Indent_Anchor_Position anchor = find_anchor_token(app, buffer, tokens, first_line, tab_width);
|
|
Token *token_ptr = anchor.token;
|
|
Indent_Parse_State indent = {};
|
|
indent.current_indent = anchor.indentation;
|
|
|
|
if (token_ptr == 0){
|
|
for (i64 line_index = first_line; line_index < one_past_last_line; ++line_index){
|
|
indent_marks[line_index] = 0;
|
|
}
|
|
}
|
|
else{
|
|
i64 line_number = get_line_number_from_pos(app, buffer, token_ptr->pos);
|
|
line_number = clamp_top(line_number, first_line);
|
|
|
|
if (token_ptr == tokens.tokens){
|
|
indent.current_indent = 0;
|
|
}
|
|
|
|
i64 next_line_start_pos = get_line_start_pos(app, buffer, line_number);
|
|
indent.previous_line_indent = indent.current_indent;
|
|
Token prev_token = {};
|
|
Token token = {};
|
|
if (token_ptr < tokens.tokens + tokens.count){
|
|
token = *token_ptr;
|
|
}
|
|
|
|
// Back up and consume this token too IF it is a scope opener.
|
|
if (token.kind == TokenBaseKind_ScopeOpen){
|
|
--token_ptr;
|
|
}
|
|
|
|
// LOOP OVER TOKENS
|
|
for (;;){
|
|
if (line_number >= one_past_last_line){
|
|
break;
|
|
}
|
|
|
|
prev_token = token;
|
|
++token_ptr;
|
|
if (token_ptr < tokens.tokens + tokens.count){
|
|
token = *token_ptr;
|
|
}
|
|
else{
|
|
block_zero_struct(&token);
|
|
token.kind = TokenBaseKind_EOF;
|
|
token.pos = buffer_get_size(app, buffer);
|
|
}
|
|
|
|
for (;token.pos >= next_line_start_pos && line_number < one_past_last_line;){
|
|
next_line_start_pos = get_line_start_pos(app, buffer, line_number + 1);
|
|
|
|
i64 this_indent = 0;
|
|
i64 previous_indent = indent.previous_line_indent;
|
|
|
|
i64 this_line_start = get_line_start_pos(app, buffer, line_number);
|
|
i64 next_line_start = next_line_start_pos;
|
|
|
|
b32 did_multi_line_behavior = false;
|
|
|
|
// NOTE(allen): Check for multi-line tokens
|
|
if (prev_token.pos <= this_line_start && prev_token.pos + prev_token.size > this_line_start){
|
|
if (prev_token.kind == TokenBaseKind_Comment || prev_token.kind == TokenBaseKind_LiteralString){
|
|
Indent_Info hard_start = get_indent_info_line_start(app, buffer, this_line_start, tab_width);
|
|
|
|
if (exact_align){
|
|
this_indent = indent.previous_comment_indent;
|
|
}
|
|
else{
|
|
if (hard_start.is_blank){
|
|
this_indent = previous_indent;
|
|
}
|
|
else{
|
|
i64 line_pos = hard_start.first_char_pos - this_line_start;
|
|
this_indent = line_pos + indent.comment_shift;
|
|
if (this_indent < 0){
|
|
this_indent = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hard_start.is_blank){
|
|
if (line_number >= first_line){
|
|
indent.previous_comment_indent = this_indent;
|
|
}
|
|
else{
|
|
indent.previous_comment_indent = hard_start.indent_pos;
|
|
}
|
|
}
|
|
|
|
did_multi_line_behavior = true;
|
|
}
|
|
}
|
|
|
|
if (!did_multi_line_behavior){
|
|
this_indent = indent.current_indent;
|
|
if (token.pos < next_line_start){
|
|
switch (token.kind){
|
|
case TokenBaseKind_Preprocessor:
|
|
{
|
|
this_indent = 0;
|
|
}break;
|
|
|
|
case TokenBaseKind_ScopeClose:
|
|
{
|
|
this_indent -= tab_width;
|
|
}break;
|
|
case TokenBaseKind_ScopeOpen:
|
|
{}break;
|
|
|
|
default:
|
|
if (indent.current_indent > 0){
|
|
b32 statement_complete = false;
|
|
|
|
Token *prev_usable_token_ptr = token_ptr - 1;
|
|
Token prev_usable_token = {};
|
|
if (prev_usable_token_ptr >= tokens.tokens){
|
|
prev_usable_token = *prev_usable_token_ptr;
|
|
}
|
|
|
|
// Scan backwards for the previous token that actually tells us about the statement.
|
|
b32 has_prev_usable_token = true;
|
|
#define NotUsable(T) \
|
|
(((T).flags&TokenBaseFlag_PreprocessorBody) || ((T).kind == TokenBaseKind_Comment) || ((T).kind == TokenBaseKind_Whitespace))
|
|
if (NotUsable(prev_usable_token)){
|
|
has_prev_usable_token = false;
|
|
|
|
for (--prev_usable_token_ptr;
|
|
prev_usable_token_ptr >= tokens.tokens;
|
|
--prev_usable_token_ptr){
|
|
|
|
prev_usable_token = *prev_usable_token_ptr;
|
|
if (!NotUsable(prev_usable_token)){
|
|
has_prev_usable_token = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#undef NotUsable
|
|
|
|
if (!has_prev_usable_token){
|
|
statement_complete = true;
|
|
}
|
|
else{
|
|
if (prev_usable_token.kind == TokenBaseKind_ScopeOpen ||
|
|
prev_usable_token.kind == TokenBaseKind_ScopeClose ||
|
|
prev_usable_token.sub_kind == TokenCppKind_Semicolon ||
|
|
prev_usable_token.sub_kind == TokenCppKind_Colon ||
|
|
prev_usable_token.sub_kind == TokenCppKind_Comma){
|
|
statement_complete = true;
|
|
}
|
|
}
|
|
|
|
if (!statement_complete){
|
|
this_indent += tab_width;
|
|
}
|
|
}break;
|
|
}
|
|
}
|
|
if (this_indent < 0){
|
|
this_indent = 0;
|
|
}
|
|
}
|
|
|
|
if (indent.paren_nesting > 0){
|
|
if (prev_token.kind != TokenBaseKind_ParentheticalOpen){
|
|
i64 level = indent.paren_nesting - 1;
|
|
level = clamp_top(level, ArrayCount(indent.paren_anchor_indent) - 1);
|
|
this_indent = indent.paren_anchor_indent[level];
|
|
}
|
|
}
|
|
|
|
// Rebase the paren anchor if the first token
|
|
// after the open paren is on the next line.
|
|
if (indent.paren_nesting > 0 &&
|
|
prev_token.kind == TokenBaseKind_ParentheticalOpen){
|
|
i64 level = indent.paren_nesting - 1;
|
|
level = clamp_top(level, ArrayCount(indent.paren_anchor_indent) - 1);
|
|
indent.paren_anchor_indent[level] = this_indent;
|
|
}
|
|
|
|
if (line_number >= first_line){
|
|
indent_marks[line_number] = this_indent;
|
|
}
|
|
++line_number;
|
|
|
|
indent.previous_line_indent = this_indent;
|
|
}
|
|
|
|
// Update indent state.
|
|
switch (token.kind){
|
|
case TokenBaseKind_ScopeOpen: indent.current_indent += tab_width; break;
|
|
case TokenBaseKind_ScopeClose: indent.current_indent -= tab_width; break;
|
|
|
|
case TokenBaseKind_Comment:
|
|
case TokenBaseKind_LiteralString:
|
|
{
|
|
i64 line = get_line_number_from_pos(app, buffer, token.pos);
|
|
i64 start = get_line_start_pos(app, buffer, line);
|
|
Indent_Info hard_start = get_indent_info_line_start(app, buffer, start, tab_width);
|
|
|
|
i64 old_dist_to_token = (token.pos - start);
|
|
i64 old_indent = hard_start.indent_pos;
|
|
i64 token_start_inset = old_dist_to_token - old_indent;
|
|
i64 new_dist_to_token = indent.current_indent + token_start_inset;
|
|
|
|
indent.comment_shift = (new_dist_to_token - old_dist_to_token);
|
|
indent.previous_comment_indent = old_indent;
|
|
}break;
|
|
|
|
case TokenBaseKind_ParentheticalOpen:
|
|
{
|
|
if (!HasFlag(token.flags, TokenBaseFlag_PreprocessorBody)){
|
|
if (indent.paren_nesting < ArrayCount(indent.paren_anchor_indent)){
|
|
i64 line = get_line_number_from_pos(app, buffer, token.pos);
|
|
i64 start = get_line_start_pos(app, buffer, line);
|
|
i64 char_pos = token.pos - start;
|
|
|
|
Indent_Info hard_start = get_indent_info_line_start(app, buffer, start, tab_width);
|
|
|
|
i64 line_pos = hard_start.first_char_pos - start;
|
|
|
|
indent.paren_anchor_indent[indent.paren_nesting] = char_pos - line_pos + indent.previous_line_indent + 1;
|
|
}
|
|
++indent.paren_nesting;
|
|
}
|
|
}break;
|
|
|
|
case TokenBaseKind_ParentheticalClose:
|
|
{
|
|
if (!HasFlag(token.flags, TokenBaseFlag_PreprocessorBody)){
|
|
if (indent.paren_nesting > 0){
|
|
--indent.paren_nesting;
|
|
}
|
|
}
|
|
}break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unshift the indent_marks array.
|
|
indent_marks += first_line;
|
|
return(indent_marks);
|
|
}
|
|
|
|
// TODO(allen): replace these with new range operators.
|
|
internal void
|
|
get_indent_lines_minimum(Application_Links *app, Buffer_ID buffer, i64 start_pos, i64 end_pos, i64 *line_start_out, i64 *line_end_out){
|
|
i64 line_start = get_line_number_from_pos(app, buffer, start_pos);
|
|
i64 line_end = get_line_number_from_pos(app, buffer, end_pos) + 1;
|
|
*line_start_out = line_start;
|
|
*line_end_out = line_end;
|
|
}
|
|
|
|
internal void
|
|
get_indent_lines_whole_tokens(Application_Links *app, Buffer_ID buffer, Token_Array tokens, i64 start_pos, i64 end_pos, i64 *line_start_out, i64 *line_end_out){
|
|
i64 line_start = get_line_number_from_pos(app, buffer, start_pos);
|
|
i64 line_end = get_line_number_from_pos(app, buffer, end_pos);
|
|
|
|
for (;line_start > 1;){
|
|
i64 line_start_pos = get_line_start_pos(app, buffer, line_start);
|
|
Token *token = get_first_token_from_pos(tokens, line_start_pos);
|
|
if (token != 0 && token->pos < line_start_pos){
|
|
line_start = get_line_number_from_pos(app, buffer, token->pos);
|
|
}
|
|
else{
|
|
break;
|
|
}
|
|
}
|
|
|
|
i64 line_count = buffer_get_line_count(app, buffer);
|
|
|
|
for (;line_end < line_count;){
|
|
i64 next_line_start_pos = get_line_start_pos(app, buffer, line_end + 1);
|
|
Token *token = get_first_token_from_pos(tokens, next_line_start_pos);
|
|
if (token != 0 && token->pos < next_line_start_pos){
|
|
line_end = get_line_number_from_pos(app, buffer, token->pos + token->size);
|
|
}
|
|
else{
|
|
break;
|
|
}
|
|
}
|
|
|
|
line_end = clamp_top(line_end, line_count);
|
|
line_end += 1;
|
|
|
|
*line_start_out = line_start;
|
|
*line_end_out = line_end;
|
|
}
|
|
|
|
internal b32
|
|
buffer_auto_indent(Application_Links *app, Buffer_ID buffer, i64 start, i64 end, i32 tab_width, Auto_Indent_Flag flags){
|
|
b32 result = false;
|
|
|
|
Managed_Scope scope = buffer_get_managed_scope(app, buffer);
|
|
|
|
Token_Array *tokens_ptr = scope_attachment(app, scope, attachment_tokens, Token_Array);
|
|
if (tokens_ptr != 0 && tokens_ptr->count != 0){
|
|
Scratch_Block scratch(app);
|
|
Token_Array tokens = *tokens_ptr;
|
|
|
|
i64 line_start = 0;
|
|
i64 line_end = 0;
|
|
if (HasFlag(flags, AutoIndent_FullTokens)){
|
|
get_indent_lines_whole_tokens(app, buffer, tokens, start, end, &line_start, &line_end);
|
|
}
|
|
else{
|
|
get_indent_lines_minimum(app, buffer, start, end, &line_start, &line_end);
|
|
}
|
|
|
|
i64 *indent_marks = get_indentation_marks(app, scratch, buffer, tokens, line_start, line_end, (flags & AutoIndent_ExactAlignBlock), tab_width);
|
|
|
|
Indent_Options opts = {};
|
|
opts.empty_blank_lines = (flags & AutoIndent_ClearLine);
|
|
opts.use_tabs = (flags & AutoIndent_UseTab);
|
|
opts.tab_width = tab_width;
|
|
|
|
set_line_indents(app, scratch, buffer, line_start, line_end, indent_marks, opts);
|
|
|
|
result = true;
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
//
|
|
// Commands
|
|
//
|
|
|
|
#if !defined(DEFAULT_INDENT_FLAGS)
|
|
# define DEFAULT_INDENT_FLAGS ((global_config.indent_with_tabs)?(AutoIndent_UseTab):(0))
|
|
#endif
|
|
|
|
#if !defined(DEF_TAB_WIDTH)
|
|
# define DEF_TAB_WIDTH global_config.indent_width
|
|
#endif
|
|
|
|
CUSTOM_COMMAND_SIG(auto_tab_whole_file)
|
|
CUSTOM_DOC("Audo-indents the entire current buffer.")
|
|
{
|
|
View_ID view = get_active_view(app, AccessOpen);
|
|
Buffer_ID buffer = view_get_buffer(app, view, AccessOpen);
|
|
i32 buffer_size = (i32)buffer_get_size(app, buffer);
|
|
buffer_auto_indent(app, buffer, 0, buffer_size, DEF_TAB_WIDTH, DEFAULT_INDENT_FLAGS | AutoIndent_FullTokens);
|
|
}
|
|
|
|
CUSTOM_COMMAND_SIG(auto_tab_line_at_cursor)
|
|
CUSTOM_DOC("Auto-indents the line on which the cursor sits.")
|
|
{
|
|
View_ID view = get_active_view(app, AccessOpen);
|
|
Buffer_ID buffer = view_get_buffer(app, view, AccessOpen);
|
|
i64 pos = view_get_cursor_pos(app, view);
|
|
buffer_auto_indent(app, buffer, pos, pos, DEF_TAB_WIDTH, DEFAULT_INDENT_FLAGS | AutoIndent_FullTokens);
|
|
move_past_lead_whitespace(app, view, buffer);
|
|
}
|
|
|
|
CUSTOM_COMMAND_SIG(auto_tab_range)
|
|
CUSTOM_DOC("Auto-indents the range between the cursor and the mark.")
|
|
{
|
|
View_ID view = get_active_view(app, AccessOpen);
|
|
Buffer_ID buffer = view_get_buffer(app, view, AccessOpen);
|
|
Range_i64 range = get_view_range(app, view);
|
|
buffer_auto_indent(app, buffer, range.min, range.max, DEF_TAB_WIDTH, DEFAULT_INDENT_FLAGS | AutoIndent_FullTokens);
|
|
move_past_lead_whitespace(app, view, buffer);
|
|
}
|
|
|
|
CUSTOM_COMMAND_SIG(write_and_auto_tab)
|
|
CUSTOM_DOC("Inserts a character and auto-indents the line on which the cursor sits.")
|
|
{
|
|
write_character(app);
|
|
View_ID view = get_active_view(app, AccessOpen);
|
|
Buffer_ID buffer = view_get_buffer(app, view, AccessOpen);
|
|
u32 flags = DEFAULT_INDENT_FLAGS;
|
|
User_Input in = get_command_input(app);
|
|
if (in.key.character == '\n'){
|
|
flags |= AutoIndent_ExactAlignBlock;
|
|
}
|
|
i64 pos = view_get_cursor_pos(app, view);
|
|
buffer_auto_indent(app, buffer, pos, pos, DEF_TAB_WIDTH, flags);
|
|
move_past_lead_whitespace(app, view, buffer);
|
|
}
|
|
|
|
// BOTTOM
|
|
|