mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
Split shell into multiple files.
This commit is contained in:
parent
a5d050fc49
commit
6a177d4d79
9 changed files with 1230 additions and 994 deletions
10
sh/Makefile
10
sh/Makefile
|
@ -12,6 +12,12 @@ CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti
|
|||
|
||||
BINARIES:=sh sortix-sh
|
||||
|
||||
SORTIX_SH_SRCS=\
|
||||
editline.cpp \
|
||||
sh.cpp \
|
||||
showline.cpp \
|
||||
util.cpp
|
||||
|
||||
all: $(BINARIES)
|
||||
|
||||
.PHONY: all install clean
|
||||
|
@ -20,8 +26,8 @@ install: all
|
|||
mkdir -p $(DESTDIR)$(BINDIR)
|
||||
install $(BINARIES) $(DESTDIR)$(BINDIR)
|
||||
|
||||
sortix-sh: sh.cpp
|
||||
$(CXX) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) $< -o $@
|
||||
sortix-sh: $(SORTIX_SH_SRCS) *.h
|
||||
$(CXX) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) $(SORTIX_SH_SRCS) -o $@
|
||||
|
||||
sh: proper-sh.cpp
|
||||
$(CXX) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) $< -o $@
|
||||
|
|
621
sh/editline.cpp
Normal file
621
sh/editline.cpp
Normal file
|
@ -0,0 +1,621 @@
|
|||
/*******************************************************************************
|
||||
|
||||
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
editline.h
|
||||
Read a line from the terminal.
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#include <sys/keycodes.h>
|
||||
#include <sys/termmode.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <wchar.h>
|
||||
#include <wctype.h>
|
||||
|
||||
#include "editline.h"
|
||||
#include "showline.h"
|
||||
|
||||
static const unsigned int NORMAL_TERMMODE =
|
||||
TERMMODE_UNICODE |
|
||||
TERMMODE_SIGNAL |
|
||||
TERMMODE_UTF8 |
|
||||
TERMMODE_LINEBUFFER |
|
||||
TERMMODE_ECHO;
|
||||
|
||||
void edit_line_show(struct edit_line* edit_state)
|
||||
{
|
||||
size_t line_length = 0;
|
||||
|
||||
mbstate_t ps;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
|
||||
line_length += strlen(edit_state->ps1);
|
||||
|
||||
for ( size_t i = 0; i < edit_state->line_used; i++ )
|
||||
{
|
||||
char mb[MB_CUR_MAX];
|
||||
line_length += wcrtomb(mb, edit_state->line[i], &ps);
|
||||
if ( edit_state->line[i] == L'\n' )
|
||||
line_length += strlen(edit_state->ps2);
|
||||
}
|
||||
|
||||
char* line = (char*) malloc(line_length + 1);
|
||||
assert(line);
|
||||
|
||||
size_t cursor = 0;
|
||||
size_t line_offset = 0;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
|
||||
strcpy(line + line_offset, edit_state->ps1);
|
||||
line_offset += strlen(edit_state->ps1);
|
||||
|
||||
for ( size_t i = 0; i < edit_state->line_used; i++ )
|
||||
{
|
||||
if ( edit_state->line_offset == i )
|
||||
cursor = line_offset;
|
||||
line_offset += wcrtomb(line + line_offset, edit_state->line[i], &ps);
|
||||
if ( edit_state->line[i] == L'\n' )
|
||||
{
|
||||
strcpy(line + line_offset, edit_state->ps2);
|
||||
line_offset += strlen(edit_state->ps2);
|
||||
}
|
||||
}
|
||||
|
||||
if ( edit_state->line_offset == edit_state->line_used )
|
||||
cursor = line_offset;
|
||||
|
||||
line[line_offset] = '\0';
|
||||
|
||||
show_line(&edit_state->show_state, line, cursor);
|
||||
|
||||
free(line);
|
||||
}
|
||||
|
||||
char* edit_line_result(struct edit_line* edit_state)
|
||||
{
|
||||
size_t result_length = 0;
|
||||
|
||||
mbstate_t ps;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
|
||||
for ( size_t i = 0; i < edit_state->line_used; i++ )
|
||||
{
|
||||
char mb[MB_CUR_MAX];
|
||||
result_length += wcrtomb(mb, edit_state->line[i], &ps);
|
||||
}
|
||||
|
||||
char* result = (char*) malloc(result_length + 1);
|
||||
if ( !result )
|
||||
return NULL;
|
||||
size_t result_offset = 0;
|
||||
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
|
||||
for ( size_t i = 0; i < edit_state->line_used; i++ )
|
||||
result_offset += wcrtomb(result + result_offset, edit_state->line[i], &ps);
|
||||
|
||||
result[result_offset] = '\0';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool edit_line_can_finish(struct edit_line* edit_state)
|
||||
{
|
||||
if ( !edit_state->check_input_incomplete )
|
||||
return true;
|
||||
char* line = edit_line_result(edit_state);
|
||||
assert(line);
|
||||
bool result = !edit_state->check_input_incomplete(
|
||||
edit_state->check_input_incomplete_context, line);
|
||||
free(line);
|
||||
return result;
|
||||
}
|
||||
|
||||
void edit_line_append_history(struct edit_line* edit_state, const char* line)
|
||||
{
|
||||
if ( edit_state->history_used == edit_state->history_length )
|
||||
{
|
||||
size_t new_length = 2 * edit_state->history_length;
|
||||
if ( new_length == 0 )
|
||||
new_length = 16;
|
||||
// TODO: Use reallocarray instead of realloc.
|
||||
size_t new_size = sizeof(char*) * new_length;
|
||||
char** new_history = (char**) realloc(edit_state->history, new_size);
|
||||
assert(new_history);
|
||||
edit_state->history = new_history;
|
||||
edit_state->history_length = new_length;
|
||||
}
|
||||
|
||||
size_t history_index = edit_state->history_used++;
|
||||
edit_state->history[history_index] = strdup(line);
|
||||
assert(edit_state->history[history_index]);
|
||||
}
|
||||
|
||||
void edit_line_type_use_record(struct edit_line* edit_state, const char* record)
|
||||
{
|
||||
free(edit_state->line);
|
||||
edit_state->line_offset = 0;
|
||||
edit_state->line_used = 0;
|
||||
edit_state->line_length = 0;
|
||||
|
||||
size_t line_length;
|
||||
|
||||
mbstate_t ps;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
|
||||
size_t record_offset = 0;
|
||||
for ( line_length = 0; true; line_length++ )
|
||||
{
|
||||
size_t num_bytes = mbrtowc(NULL, record + record_offset, SIZE_MAX, &ps);
|
||||
assert(num_bytes != (size_t) -2);
|
||||
assert(num_bytes != (size_t) -1);
|
||||
if ( num_bytes == 0 )
|
||||
break;
|
||||
record_offset += num_bytes;
|
||||
}
|
||||
|
||||
// TODO: Avoid multiplication overflow.
|
||||
wchar_t* line = (wchar_t*) malloc(sizeof(wchar_t) * line_length);
|
||||
assert(line);
|
||||
size_t line_used;
|
||||
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
|
||||
record_offset = 0;
|
||||
for ( line_used = 0; line_used < line_length; line_used++ )
|
||||
{
|
||||
size_t num_bytes = mbrtowc(&line[line_used], record + record_offset, SIZE_MAX, &ps);
|
||||
assert(num_bytes != (size_t) -2);
|
||||
assert(num_bytes != (size_t) -1);
|
||||
assert(num_bytes != (size_t) 0);
|
||||
record_offset += num_bytes;
|
||||
}
|
||||
|
||||
edit_state->line = line;
|
||||
edit_state->line_offset = line_used;
|
||||
edit_state->line_used = line_used;
|
||||
edit_state->line_length = line_length;
|
||||
}
|
||||
|
||||
void edit_line_type_history_save_at(struct edit_line* edit_state, size_t index)
|
||||
{
|
||||
assert(index <= edit_state->history_used);
|
||||
|
||||
char* saved_line = edit_line_result(edit_state);
|
||||
assert(saved_line);
|
||||
if ( index == edit_state->history_used )
|
||||
{
|
||||
edit_line_append_history(edit_state, saved_line);
|
||||
free(saved_line);
|
||||
}
|
||||
else
|
||||
{
|
||||
free(edit_state->history[index]);
|
||||
edit_state->history[index] = saved_line;
|
||||
}
|
||||
}
|
||||
|
||||
void edit_line_type_history_save_current(struct edit_line* edit_state)
|
||||
{
|
||||
edit_line_type_history_save_at(edit_state, edit_state->history_offset);
|
||||
}
|
||||
|
||||
void edit_line_type_history_prev(struct edit_line* edit_state)
|
||||
{
|
||||
if ( edit_state->history_offset == 0 )
|
||||
return;
|
||||
|
||||
edit_line_type_history_save_current(edit_state);
|
||||
|
||||
const char* record = edit_state->history[--edit_state->history_offset];
|
||||
assert(record);
|
||||
edit_line_type_use_record(edit_state, record);
|
||||
}
|
||||
|
||||
void edit_line_type_history_next(struct edit_line* edit_state)
|
||||
{
|
||||
if ( edit_state->history_used - edit_state->history_offset <= 1 )
|
||||
return;
|
||||
|
||||
edit_line_type_history_save_current(edit_state);
|
||||
|
||||
const char* record = edit_state->history[++edit_state->history_offset];
|
||||
assert(record);
|
||||
edit_line_type_use_record(edit_state, record);
|
||||
}
|
||||
|
||||
void edit_line_type_codepoint(struct edit_line* edit_state, wchar_t wc)
|
||||
{
|
||||
if ( wc == L'\n' && edit_line_can_finish(edit_state))
|
||||
{
|
||||
if ( edit_state->line_used )
|
||||
edit_line_type_history_save_at(edit_state, edit_state->history_target);
|
||||
edit_state->editing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( edit_state->line_used == edit_state->line_length )
|
||||
{
|
||||
size_t new_length = 2 * edit_state->line_length;
|
||||
if ( !new_length )
|
||||
new_length = 16;
|
||||
// TODO: Use reallocarray instead of realloc.
|
||||
size_t new_size = sizeof(wchar_t) * new_length;
|
||||
wchar_t* new_line = (wchar_t*) realloc(edit_state->line, new_size);
|
||||
assert(new_line);
|
||||
edit_state->line = new_line;
|
||||
edit_state->line_length = new_length;
|
||||
}
|
||||
|
||||
assert(edit_state->line_offset <= edit_state->line_used);
|
||||
assert(edit_state->line_used <= edit_state->line_length);
|
||||
|
||||
for ( size_t i = edit_state->line_used; i != edit_state->line_offset; i-- )
|
||||
edit_state->line[i] = edit_state->line[i-1];
|
||||
|
||||
edit_state->line[edit_state->line_used++, edit_state->line_offset++] = wc;
|
||||
|
||||
assert(edit_state->line_offset <= edit_state->line_used);
|
||||
assert(edit_state->line_used <= edit_state->line_length);
|
||||
}
|
||||
|
||||
void line_edit_type_home(struct edit_line* edit_state)
|
||||
{
|
||||
edit_state->line_offset = 0;
|
||||
}
|
||||
|
||||
void line_edit_type_left(struct edit_line* edit_state)
|
||||
{
|
||||
if ( edit_state->line_offset == 0 )
|
||||
return;
|
||||
edit_state->line_offset--;
|
||||
}
|
||||
|
||||
void line_edit_type_right(struct edit_line* edit_state)
|
||||
{
|
||||
if ( edit_state->line_offset == edit_state->line_used )
|
||||
return;
|
||||
edit_state->line_offset++;
|
||||
}
|
||||
|
||||
void line_edit_type_end(struct edit_line* edit_state)
|
||||
{
|
||||
edit_state->line_offset = edit_state->line_used;
|
||||
}
|
||||
|
||||
void line_edit_type_backspace(struct edit_line* edit_state)
|
||||
{
|
||||
if ( edit_state->line_offset == 0 )
|
||||
return;
|
||||
edit_state->line_used--;
|
||||
edit_state->line_offset--;
|
||||
for ( size_t i = edit_state->line_offset; i < edit_state->line_used; i++ )
|
||||
edit_state->line[i] = edit_state->line[i+1];
|
||||
}
|
||||
|
||||
void line_edit_type_previous_word(struct edit_line* edit_state)
|
||||
{
|
||||
while ( edit_state->line_offset &&
|
||||
iswspace(edit_state->line[edit_state->line_offset-1]) )
|
||||
edit_state->line_offset--;
|
||||
while ( edit_state->line_offset &&
|
||||
!iswspace(edit_state->line[edit_state->line_offset-1]) )
|
||||
edit_state->line_offset--;
|
||||
}
|
||||
|
||||
void line_edit_type_next_word(struct edit_line* edit_state)
|
||||
{
|
||||
while ( edit_state->line_offset != edit_state->line_used &&
|
||||
iswspace(edit_state->line[edit_state->line_offset]) )
|
||||
edit_state->line_offset++;
|
||||
while ( edit_state->line_offset != edit_state->line_used &&
|
||||
!iswspace(edit_state->line[edit_state->line_offset]) )
|
||||
edit_state->line_offset++;
|
||||
}
|
||||
|
||||
void line_edit_type_delete(struct edit_line* edit_state)
|
||||
{
|
||||
if ( edit_state->line_offset == edit_state->line_used )
|
||||
return;
|
||||
edit_state->line_used--;
|
||||
for ( size_t i = edit_state->line_offset; i < edit_state->line_used; i++ )
|
||||
edit_state->line[i] = edit_state->line[i+1];
|
||||
}
|
||||
|
||||
void line_edit_type_eof_or_delete(struct edit_line* edit_state)
|
||||
{
|
||||
if ( edit_state->line_used )
|
||||
return line_edit_type_delete(edit_state);
|
||||
edit_state->editing = false;
|
||||
edit_state->eof_condition = true;
|
||||
if ( edit_state->trap_eof_opportunity )
|
||||
edit_state->trap_eof_opportunity(edit_state->trap_eof_opportunity_context);
|
||||
}
|
||||
|
||||
void edit_line_type_interrupt(struct edit_line* edit_state)
|
||||
{
|
||||
dprintf(edit_state->out_fd, "^C\n");
|
||||
edit_state->editing = false;
|
||||
edit_state->abort_editing = true;
|
||||
}
|
||||
|
||||
void edit_line_type_kill_after(struct edit_line* edit_state)
|
||||
{
|
||||
while ( edit_state->line_offset < edit_state->line_used )
|
||||
line_edit_type_delete(edit_state);
|
||||
}
|
||||
|
||||
void edit_line_type_kill_before(struct edit_line* edit_state)
|
||||
{
|
||||
while ( edit_state->line_offset )
|
||||
line_edit_type_backspace(edit_state);
|
||||
}
|
||||
|
||||
void edit_line_type_clear(struct edit_line* edit_state)
|
||||
{
|
||||
show_line_clear(&edit_state->show_state);
|
||||
}
|
||||
|
||||
void edit_line_type_delete_word_before(struct edit_line* edit_state)
|
||||
{
|
||||
while ( edit_state->line_offset &&
|
||||
iswspace(edit_state->line[edit_state->line_offset-1]) )
|
||||
line_edit_type_backspace(edit_state);
|
||||
while ( edit_state->line_offset &&
|
||||
!iswspace(edit_state->line[edit_state->line_offset-1]) )
|
||||
line_edit_type_backspace(edit_state);
|
||||
}
|
||||
|
||||
int edit_line_completion_sort(const void* a_ptr, const void* b_ptr)
|
||||
{
|
||||
const char* a = *(const char**) a_ptr;
|
||||
const char* b = *(const char**) b_ptr;
|
||||
return strcmp(a, b);
|
||||
}
|
||||
|
||||
void edit_line_type_complete(struct edit_line* edit_state)
|
||||
{
|
||||
if ( !edit_state->complete )
|
||||
return;
|
||||
|
||||
char* partial = edit_line_result(edit_state);
|
||||
if ( !partial )
|
||||
return;
|
||||
|
||||
mbstate_t ps;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
|
||||
size_t complete_at = 0;
|
||||
for ( size_t i = 0; i < edit_state->line_offset; i++ )
|
||||
{
|
||||
char mb[MB_CUR_MAX];
|
||||
size_t num_bytes = wcrtomb(mb, edit_state->line[i], &ps);
|
||||
assert(num_bytes != (size_t) -1);
|
||||
assert(num_bytes != (size_t) 0);
|
||||
complete_at += num_bytes;
|
||||
}
|
||||
|
||||
char** completions;
|
||||
size_t used_before;
|
||||
size_t used_after;
|
||||
size_t num_completions = edit_state->complete(
|
||||
&completions,
|
||||
&used_before,
|
||||
&used_after,
|
||||
edit_state->complete_context,
|
||||
partial,
|
||||
complete_at);
|
||||
|
||||
qsort(completions, num_completions, sizeof(char*), edit_line_completion_sort);
|
||||
|
||||
size_t lcp = 0;
|
||||
bool similar = true;
|
||||
while ( num_completions && similar )
|
||||
{
|
||||
char c = completions[0][lcp];
|
||||
if ( c == '\0' )
|
||||
break;
|
||||
for ( size_t i = 1; similar && i < num_completions; i++ )
|
||||
{
|
||||
if ( completions[i][lcp] != c )
|
||||
similar = false;
|
||||
}
|
||||
if ( similar )
|
||||
lcp++;
|
||||
}
|
||||
|
||||
bool prefix_ends_with_slash = false;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
for ( size_t i = 0; i < lcp; )
|
||||
{
|
||||
const char* completion = completions[0];
|
||||
wchar_t wc;
|
||||
size_t num_bytes = mbrtowc(&wc, completion + i, lcp - i, &ps);
|
||||
if ( num_bytes == (size_t) -2 )
|
||||
break;
|
||||
assert(num_bytes != (size_t) -1);
|
||||
assert(num_bytes != (size_t) 0);
|
||||
edit_line_type_codepoint(edit_state, wc);
|
||||
prefix_ends_with_slash = wc == L'/';
|
||||
i += num_bytes;
|
||||
}
|
||||
|
||||
if ( num_completions == 1 && !prefix_ends_with_slash )
|
||||
{
|
||||
edit_line_type_codepoint(edit_state, ' ');
|
||||
}
|
||||
|
||||
if ( 2 <= num_completions && lcp == 0 && edit_state->double_tab )
|
||||
{
|
||||
bool first = true;
|
||||
for ( size_t i = 0; i < num_completions; i++ )
|
||||
{
|
||||
const char* completion = completions[i];
|
||||
size_t length = used_before + strlen(completion) + used_after;
|
||||
if ( !length )
|
||||
continue;
|
||||
if ( first )
|
||||
show_line_finish(&edit_state->show_state);
|
||||
// TODO: Use a reliable write.
|
||||
if ( !first )
|
||||
write(edit_state->out_fd, " ", 1);
|
||||
write(edit_state->out_fd, partial + complete_at - used_before, used_before);
|
||||
write(edit_state->out_fd, completion, strlen(completion));
|
||||
write(edit_state->out_fd, partial + complete_at, used_after);
|
||||
first = false;
|
||||
}
|
||||
if ( !first)
|
||||
{
|
||||
write(edit_state->out_fd, "\n", 1);
|
||||
show_line_begin(&edit_state->show_state, edit_state->out_fd);
|
||||
edit_line_show(edit_state);
|
||||
}
|
||||
}
|
||||
|
||||
edit_state->double_tab = true;
|
||||
|
||||
(void) used_before;
|
||||
(void) used_after;
|
||||
|
||||
for ( size_t i = 0; i < num_completions; i++ )
|
||||
free(completions[i]);
|
||||
free(completions);
|
||||
|
||||
free(partial);
|
||||
}
|
||||
|
||||
void edit_line_kbkey(struct edit_line* edit_state, int kbkey)
|
||||
{
|
||||
if ( kbkey != KBKEY_TAB && kbkey != -KBKEY_TAB )
|
||||
edit_state->double_tab = false;
|
||||
|
||||
if ( edit_state->left_control || edit_state->right_control )
|
||||
{
|
||||
switch ( kbkey )
|
||||
{
|
||||
case KBKEY_LEFT: line_edit_type_previous_word(edit_state); return;
|
||||
case KBKEY_RIGHT: line_edit_type_next_word(edit_state); return;
|
||||
};
|
||||
}
|
||||
|
||||
switch ( kbkey )
|
||||
{
|
||||
case KBKEY_HOME: line_edit_type_home(edit_state); return;
|
||||
case KBKEY_LEFT: line_edit_type_left(edit_state); return;
|
||||
case KBKEY_RIGHT: line_edit_type_right(edit_state); return;
|
||||
case KBKEY_UP: edit_line_type_history_prev(edit_state); return;
|
||||
case KBKEY_DOWN: edit_line_type_history_next(edit_state); return;
|
||||
case KBKEY_END: line_edit_type_end(edit_state); return;
|
||||
case KBKEY_BKSPC: line_edit_type_backspace(edit_state); return;
|
||||
case KBKEY_DELETE: line_edit_type_delete(edit_state); return;
|
||||
case KBKEY_TAB: edit_line_type_complete(edit_state); return;
|
||||
case -KBKEY_LCTRL: edit_state->left_control = false; return;
|
||||
case +KBKEY_LCTRL: edit_state->left_control = true; return;
|
||||
case -KBKEY_RCTRL: edit_state->right_control = false; return;
|
||||
case +KBKEY_RCTRL: edit_state->right_control = true; return;
|
||||
};
|
||||
}
|
||||
|
||||
void edit_line_codepoint(struct edit_line* edit_state, wchar_t wc)
|
||||
{
|
||||
if ( (edit_state->left_control || edit_state->right_control) &&
|
||||
((L'a' <= wc && wc <= L'z') || (L'A' <= wc && wc <= L'Z')) )
|
||||
{
|
||||
if ( wc == L'a' || wc == L'A' )
|
||||
line_edit_type_home(edit_state);
|
||||
if ( wc == L'b' || wc == L'B' )
|
||||
line_edit_type_left(edit_state);
|
||||
if ( wc == L'c' || wc == L'C' )
|
||||
edit_line_type_interrupt(edit_state);
|
||||
if ( wc == L'd' || wc == L'D' )
|
||||
line_edit_type_eof_or_delete(edit_state);
|
||||
if ( wc == L'e' || wc == L'E' )
|
||||
line_edit_type_end(edit_state);
|
||||
if ( wc == L'f' || wc == L'F' )
|
||||
line_edit_type_right(edit_state);
|
||||
if ( wc == L'k' || wc == L'K' )
|
||||
edit_line_type_kill_after(edit_state);
|
||||
if ( wc == L'l' || wc == L'L' )
|
||||
show_line_clear(&edit_state->show_state);
|
||||
if ( wc == L'u' || wc == L'U' )
|
||||
edit_line_type_kill_before(edit_state);
|
||||
if ( wc == L'w' || wc == L'W' )
|
||||
edit_line_type_delete_word_before(edit_state);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( wc == L'\b' )
|
||||
return;
|
||||
if ( wc == L'\t' )
|
||||
return;
|
||||
|
||||
edit_line_type_codepoint(edit_state, wc);
|
||||
}
|
||||
|
||||
void edit_line(struct edit_line* edit_state)
|
||||
{
|
||||
edit_state->editing = true;
|
||||
edit_state->abort_editing = false;
|
||||
edit_state->eof_condition = false;
|
||||
edit_state->double_tab = false;
|
||||
|
||||
free(edit_state->line);
|
||||
edit_state->line = NULL;
|
||||
edit_state->line_offset = 0;
|
||||
edit_state->line_used = 0;
|
||||
edit_state->line_length = 0;
|
||||
edit_state->history_offset = edit_state->history_used;
|
||||
edit_state->history_target = edit_state->history_used;
|
||||
|
||||
settermmode(edit_state->in_fd, TERMMODE_KBKEY | TERMMODE_UNICODE);
|
||||
|
||||
show_line_begin(&edit_state->show_state, edit_state->out_fd);
|
||||
|
||||
while ( edit_state->editing )
|
||||
{
|
||||
edit_line_show(edit_state);
|
||||
|
||||
uint32_t codepoint;
|
||||
if ( read(0, &codepoint, sizeof(codepoint)) != sizeof(codepoint) )
|
||||
{
|
||||
edit_state->eof_condition = true;
|
||||
edit_state->abort_editing = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( int kbkey = KBKEY_DECODE(codepoint) )
|
||||
edit_line_kbkey(edit_state, kbkey);
|
||||
else
|
||||
edit_line_codepoint(edit_state, (wchar_t) codepoint);
|
||||
}
|
||||
|
||||
if ( edit_state->abort_editing )
|
||||
show_line_abort(&edit_state->show_state);
|
||||
else
|
||||
{
|
||||
edit_line_show(edit_state);
|
||||
show_line_finish(&edit_state->show_state);
|
||||
}
|
||||
|
||||
settermmode(edit_state->in_fd, NORMAL_TERMMODE);
|
||||
}
|
91
sh/editline.h
Normal file
91
sh/editline.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*******************************************************************************
|
||||
|
||||
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
editline.h
|
||||
Read a line from the terminal.
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef EDITLINE_H
|
||||
#define EDITLINE_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "showline.h"
|
||||
|
||||
struct edit_line
|
||||
{
|
||||
const char* ps1;
|
||||
const char* ps2;
|
||||
struct show_line show_state;
|
||||
wchar_t* line;
|
||||
size_t line_offset;
|
||||
size_t line_used;
|
||||
size_t line_length;
|
||||
char** history;
|
||||
size_t history_offset;
|
||||
size_t history_used;
|
||||
size_t history_length;
|
||||
size_t history_target;
|
||||
void* check_input_incomplete_context;
|
||||
bool (*check_input_incomplete)(void*, const char*);
|
||||
void* trap_eof_opportunity_context;
|
||||
void (*trap_eof_opportunity)(void*);
|
||||
void* complete_context;
|
||||
size_t (*complete)(char***, size_t*, size_t*, void*, const char*, size_t);
|
||||
int in_fd;
|
||||
int out_fd;
|
||||
bool editing;
|
||||
bool abort_editing;
|
||||
bool eof_condition;
|
||||
bool double_tab;
|
||||
// TODO: Should these be stored here, or outside the line editing context?
|
||||
bool left_control;
|
||||
bool right_control;
|
||||
};
|
||||
|
||||
void edit_line_show(struct edit_line* edit_state);
|
||||
char* edit_line_result(struct edit_line* edit_state);
|
||||
bool edit_line_can_finish(struct edit_line* edit_state);
|
||||
void edit_line_append_history(struct edit_line* edit_state, const char* line);
|
||||
void edit_line_type_use_record(struct edit_line* edit_state, const char* record);
|
||||
void edit_line_type_history_save_at(struct edit_line* edit_state, size_t index);
|
||||
void edit_line_type_history_save_current(struct edit_line* edit_state);
|
||||
void edit_line_type_history_prev(struct edit_line* edit_state);
|
||||
void edit_line_type_history_next(struct edit_line* edit_state);
|
||||
void edit_line_type_codepoint(struct edit_line* edit_state, wchar_t wc);
|
||||
void line_edit_type_home(struct edit_line* edit_state);
|
||||
void line_edit_type_left(struct edit_line* edit_state);
|
||||
void line_edit_type_right(struct edit_line* edit_state);
|
||||
void line_edit_type_end(struct edit_line* edit_state);
|
||||
void line_edit_type_backspace(struct edit_line* edit_state);
|
||||
void line_edit_type_previous_word(struct edit_line* edit_state);
|
||||
void line_edit_type_next_word(struct edit_line* edit_state);
|
||||
void line_edit_type_delete(struct edit_line* edit_state);
|
||||
void line_edit_type_eof_or_delete(struct edit_line* edit_state);
|
||||
void edit_line_type_interrupt(struct edit_line* edit_state);
|
||||
void edit_line_type_kill_after(struct edit_line* edit_state);
|
||||
void edit_line_type_kill_before(struct edit_line* edit_state);
|
||||
void edit_line_type_clear(struct edit_line* edit_state);
|
||||
void edit_line_type_delete_word_before(struct edit_line* edit_state);
|
||||
int edit_line_completion_sort(const void* a_ptr, const void* b_ptr);
|
||||
void edit_line_type_complete(struct edit_line* edit_state);
|
||||
void edit_line_kbkey(struct edit_line* edit_state, int kbkey);
|
||||
void edit_line_codepoint(struct edit_line* edit_state, wchar_t wc);
|
||||
void edit_line(struct edit_line* edit_state);
|
||||
|
||||
#endif
|
0
sh/sh.h
Normal file
0
sh/sh.h
Normal file
377
sh/showline.cpp
Normal file
377
sh/showline.cpp
Normal file
|
@ -0,0 +1,377 @@
|
|||
/*******************************************************************************
|
||||
|
||||
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
showline.cpp
|
||||
Display a line on the terminal.
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "showline.h"
|
||||
|
||||
struct wincurpos predict_cursor(struct cursor_predict* cursor_predict,
|
||||
struct wincurpos wcp,
|
||||
struct winsize ws,
|
||||
wchar_t c)
|
||||
{
|
||||
if ( c == L'\0' )
|
||||
return wcp;
|
||||
|
||||
if ( cursor_predict->escaped )
|
||||
{
|
||||
if ( (L'a' <= c && c <= L'z') || (L'A' <= c && c <= L'Z') )
|
||||
cursor_predict->escaped = false;
|
||||
return wcp;
|
||||
}
|
||||
|
||||
if ( c == L'\e' )
|
||||
{
|
||||
cursor_predict->escaped = true;
|
||||
return wcp;
|
||||
}
|
||||
|
||||
if ( c == L'\n' || ws.ws_col <= wcp.wcp_col + 1 )
|
||||
{
|
||||
wcp.wcp_col = 0;
|
||||
if ( wcp.wcp_row + 1 < ws.ws_row )
|
||||
wcp.wcp_row++;
|
||||
}
|
||||
else
|
||||
{
|
||||
wcp.wcp_col++;
|
||||
}
|
||||
|
||||
return wcp;
|
||||
}
|
||||
|
||||
bool predict_will_scroll(struct cursor_predict cursor_predict,
|
||||
struct wincurpos wcp,
|
||||
struct winsize ws,
|
||||
wchar_t c)
|
||||
{
|
||||
if ( c == L'\0' )
|
||||
return false;
|
||||
if ( cursor_predict.escaped )
|
||||
return false;
|
||||
return (c == L'\n' || ws.ws_col <= wcp.wcp_col + 1) &&
|
||||
!(wcp.wcp_row + 1 < ws.ws_row);
|
||||
}
|
||||
|
||||
void show_line_begin(struct show_line* show_state, int out_fd)
|
||||
{
|
||||
memset(show_state, 0, sizeof(*show_state));
|
||||
show_state->out_fd = out_fd;
|
||||
show_state->current_line = NULL;
|
||||
show_state->current_cursor = 0;
|
||||
tcgetwincurpos(out_fd, &show_state->wcp_start);
|
||||
show_state->wcp_current = show_state->wcp_start;
|
||||
tcgetwinsize(show_state->out_fd, &show_state->ws);
|
||||
}
|
||||
|
||||
bool show_line_is_weird(const char* line)
|
||||
{
|
||||
for ( size_t i = 0; line[i]; i++ )
|
||||
{
|
||||
if ( line[i] == '\e' )
|
||||
{
|
||||
i++;
|
||||
if ( line[i] != '[' )
|
||||
return true;
|
||||
i++;
|
||||
while ( ('0' <= line[i] && line[i] <= '9') || line[i] == ';' )
|
||||
i++;
|
||||
switch ( line[i] )
|
||||
{
|
||||
case 'm': break;
|
||||
default: return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ( line[i] )
|
||||
{
|
||||
case '\a': return true;
|
||||
case '\b': return true;
|
||||
case '\f': return true;
|
||||
case '\r': return true;
|
||||
case '\t': return true; // TODO: This isn't weird.
|
||||
case '\v': return true;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void show_line_change_cursor(struct show_line* show_state, struct wincurpos wcp)
|
||||
{
|
||||
if ( wcp.wcp_col == show_state->wcp_current.wcp_col &&
|
||||
wcp.wcp_row == show_state->wcp_current.wcp_row )
|
||||
return;
|
||||
|
||||
if ( wcp.wcp_col == 0 )
|
||||
dprintf(show_state->out_fd, "\e[%zuH", wcp.wcp_row + 1);
|
||||
else
|
||||
dprintf(show_state->out_fd, "\e[%zu;%zuH", wcp.wcp_row + 1, wcp.wcp_col+ 1);
|
||||
|
||||
show_state->wcp_current = wcp;
|
||||
}
|
||||
|
||||
bool show_line_optimized(struct show_line* show_state, const char* line, size_t cursor)
|
||||
{
|
||||
struct winsize ws = show_state->ws;
|
||||
|
||||
mbstate_t old_ps;
|
||||
mbstate_t new_ps;
|
||||
memset(&old_ps, 0, sizeof(old_ps));
|
||||
memset(&new_ps, 0, sizeof(new_ps));
|
||||
struct wincurpos old_wcp = show_state->wcp_start;
|
||||
struct wincurpos new_wcp = show_state->wcp_start;
|
||||
struct cursor_predict old_cursor_predict;
|
||||
struct cursor_predict new_cursor_predict;
|
||||
memset(&old_cursor_predict, 0, sizeof(old_cursor_predict));
|
||||
memset(&new_cursor_predict, 0, sizeof(new_cursor_predict));
|
||||
size_t old_line_offset = 0;
|
||||
size_t new_line_offset = 0;
|
||||
const char* old_line = show_state->current_line;
|
||||
const char* new_line = line;
|
||||
|
||||
struct wincurpos cursor_wcp = show_state->wcp_start;
|
||||
|
||||
while ( true )
|
||||
{
|
||||
if ( cursor == new_line_offset )
|
||||
cursor_wcp = new_wcp;
|
||||
|
||||
wchar_t old_wc;
|
||||
wchar_t new_wc;
|
||||
|
||||
size_t old_num_bytes = mbrtowc(&old_wc, old_line + old_line_offset, SIZE_MAX, &old_ps);
|
||||
size_t new_num_bytes = mbrtowc(&new_wc, new_line + new_line_offset, SIZE_MAX, &new_ps);
|
||||
assert(old_num_bytes != (size_t) -2);
|
||||
assert(new_num_bytes != (size_t) -2);
|
||||
assert(old_num_bytes != (size_t) -1);
|
||||
assert(new_num_bytes != (size_t) -1);
|
||||
if ( old_num_bytes == 0 && new_num_bytes == 0 )
|
||||
break;
|
||||
|
||||
bool will_scroll = predict_will_scroll(new_cursor_predict, new_wcp, ws, new_wc);
|
||||
bool can_scroll = show_state->wcp_start.wcp_row != 0;
|
||||
|
||||
if ( will_scroll && !can_scroll )
|
||||
{
|
||||
if ( new_line_offset < cursor )
|
||||
cursor_wcp = new_wcp;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( predict_will_scroll(old_cursor_predict, old_wcp, ws, old_wc) )
|
||||
break;
|
||||
|
||||
struct wincurpos next_old_wcp = predict_cursor(&old_cursor_predict, old_wcp, ws, old_wc);
|
||||
struct wincurpos next_new_wcp = predict_cursor(&new_cursor_predict, new_wcp, ws, new_wc);
|
||||
|
||||
if ( old_wc != new_wc ||
|
||||
old_wcp.wcp_row != new_wcp.wcp_row ||
|
||||
old_wcp.wcp_col != new_wcp.wcp_col )
|
||||
{
|
||||
// TODO: Use a reliable write instead!
|
||||
|
||||
if ( old_wc == L'\n' && new_wc == L'\n' )
|
||||
{
|
||||
// Good enough as newlines are invisible.
|
||||
}
|
||||
else if ( old_wc == L'\n' && new_wc != L'\0' )
|
||||
{
|
||||
show_line_change_cursor(show_state, new_wcp);
|
||||
write(show_state->out_fd, new_line + new_line_offset, new_num_bytes);
|
||||
show_state->wcp_current = next_new_wcp;
|
||||
old_num_bytes = 0;
|
||||
}
|
||||
else if ( old_wc != L'\0' && new_wc == '\n' )
|
||||
{
|
||||
show_line_change_cursor(show_state, old_wcp);
|
||||
write(show_state->out_fd, " ", 1);
|
||||
show_state->wcp_current = next_old_wcp;
|
||||
new_num_bytes = 0;
|
||||
}
|
||||
else if ( old_wc == L'\n' && new_wc == L'\0' )
|
||||
{
|
||||
// No need to do anything here as newlines are visible.
|
||||
}
|
||||
else if ( old_wc == L'\0' && new_wc == L'\n' )
|
||||
{
|
||||
show_line_change_cursor(show_state, new_wcp);
|
||||
write(show_state->out_fd, new_line + new_line_offset, new_num_bytes);
|
||||
show_state->wcp_current = next_new_wcp;
|
||||
}
|
||||
else if ( old_wcp.wcp_row != new_wcp.wcp_row ||
|
||||
old_wcp.wcp_col != new_wcp.wcp_col )
|
||||
return false;
|
||||
else if ( new_wc == L'\0' && old_wc != L'\0' )
|
||||
{
|
||||
show_line_change_cursor(show_state, old_wcp);
|
||||
write(show_state->out_fd, " ", 1);
|
||||
show_state->wcp_current = next_old_wcp;
|
||||
}
|
||||
else if ( new_wc != L'\0' )
|
||||
{
|
||||
show_line_change_cursor(show_state, new_wcp);
|
||||
write(show_state->out_fd, new_line + new_line_offset, new_num_bytes);
|
||||
show_state->wcp_current = next_new_wcp;
|
||||
}
|
||||
}
|
||||
|
||||
if ( will_scroll && can_scroll )
|
||||
{
|
||||
cursor_wcp.wcp_row--;
|
||||
next_old_wcp.wcp_row--;
|
||||
show_state->wcp_start.wcp_row--;
|
||||
}
|
||||
|
||||
old_wcp = next_old_wcp;
|
||||
new_wcp = next_new_wcp;
|
||||
|
||||
old_line_offset += old_num_bytes;
|
||||
new_line_offset += new_num_bytes;
|
||||
}
|
||||
|
||||
show_line_change_cursor(show_state, cursor_wcp);
|
||||
|
||||
free(show_state->current_line);
|
||||
show_state->current_line = strdup(line);
|
||||
assert(show_state->current_line);
|
||||
show_state->current_cursor = cursor;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void show_line(struct show_line* show_state, const char* line, size_t cursor)
|
||||
{
|
||||
// TODO: We don't currently invalidate on SIGWINCH.
|
||||
struct winsize ws;
|
||||
tcgetwinsize(show_state->out_fd, &ws);
|
||||
if ( ws.ws_col != show_state->ws.ws_col ||
|
||||
ws.ws_row != show_state->ws.ws_row )
|
||||
{
|
||||
// TODO: What if wcp_start isn't inside the window any longer?
|
||||
show_state->invalidated = true;
|
||||
show_state->ws = ws;
|
||||
}
|
||||
|
||||
// Attempt to do an optimized line re-rendering reusing the characters
|
||||
// already present on the console. Bail out if this turns out to be harder
|
||||
// than expected and re-render everything from scratch instead.
|
||||
if ( !show_state->invalidated &&
|
||||
show_state->current_line &&
|
||||
!show_line_is_weird(show_state->current_line) &&
|
||||
!show_line_is_weird(line) )
|
||||
{
|
||||
if ( show_line_optimized(show_state, line, cursor) )
|
||||
return;
|
||||
show_state->invalidated = true;
|
||||
}
|
||||
|
||||
show_line_change_cursor(show_state, show_state->wcp_start);
|
||||
|
||||
dprintf(show_state->out_fd, "\e[m");
|
||||
|
||||
if ( show_state->invalidated || show_state->current_line )
|
||||
dprintf(show_state->out_fd, "\e[0J");
|
||||
|
||||
struct cursor_predict cursor_predict;
|
||||
memset(&cursor_predict, 0, sizeof(cursor_predict));
|
||||
struct wincurpos wcp = show_state->wcp_start;
|
||||
struct wincurpos cursor_wcp = wcp;
|
||||
|
||||
mbstate_t ps;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
for ( size_t i = 0; true; )
|
||||
{
|
||||
if ( cursor == i )
|
||||
cursor_wcp = wcp;
|
||||
wchar_t wc;
|
||||
size_t num_bytes = mbrtowc(&wc, line + i, SIZE_MAX, &ps);
|
||||
assert(num_bytes != (size_t) -2);
|
||||
assert(num_bytes != (size_t) -1);
|
||||
if ( num_bytes == 0 )
|
||||
break;
|
||||
bool will_scroll = predict_will_scroll(cursor_predict, wcp, ws, wc);
|
||||
bool can_scroll = show_state->wcp_start.wcp_row != 0;
|
||||
if ( will_scroll && !can_scroll )
|
||||
{
|
||||
if ( i < cursor )
|
||||
cursor_wcp = wcp;
|
||||
break;
|
||||
}
|
||||
// TODO: Use a reliable write.
|
||||
write(show_state->out_fd, line + i, num_bytes);
|
||||
if ( will_scroll && can_scroll )
|
||||
{
|
||||
cursor_wcp.wcp_row--;
|
||||
show_state->wcp_start.wcp_row--;
|
||||
}
|
||||
wcp = predict_cursor(&cursor_predict, wcp, ws, wc);
|
||||
i += num_bytes;
|
||||
}
|
||||
|
||||
dprintf(show_state->out_fd, "\e[%zu;%zuH",
|
||||
cursor_wcp.wcp_row + 1,
|
||||
cursor_wcp.wcp_col + 1);
|
||||
|
||||
show_state->wcp_current = wcp;
|
||||
|
||||
free(show_state->current_line);
|
||||
show_state->current_line = strdup(line);
|
||||
assert(show_state->current_line);
|
||||
show_state->current_cursor = cursor;
|
||||
|
||||
show_state->invalidated = false;
|
||||
}
|
||||
|
||||
void show_line_clear(struct show_line* show_state)
|
||||
{
|
||||
dprintf(show_state->out_fd, "\e[H\e[2J");
|
||||
|
||||
show_state->wcp_start.wcp_row = 0;
|
||||
show_state->wcp_start.wcp_col = 0;
|
||||
show_state->invalidated = true;
|
||||
|
||||
show_line(show_state, show_state->current_line, strlen(show_state->current_line));
|
||||
}
|
||||
|
||||
void show_line_abort(struct show_line* show_state)
|
||||
{
|
||||
free(show_state->current_line);
|
||||
show_state->current_line = NULL;
|
||||
show_state->current_cursor = 0;
|
||||
}
|
||||
|
||||
void show_line_finish(struct show_line* show_state)
|
||||
{
|
||||
show_line(show_state, show_state->current_line, strlen(show_state->current_line));
|
||||
dprintf(show_state->out_fd, "\n");
|
||||
|
||||
show_line_abort(show_state);
|
||||
}
|
63
sh/showline.h
Normal file
63
sh/showline.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*******************************************************************************
|
||||
|
||||
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
showline.h
|
||||
Display a line on the terminal.
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef SHOWLINE_H
|
||||
#define SHOWLINE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <termios.h>
|
||||
|
||||
// TODO: Predict the terminal colors as well!
|
||||
struct cursor_predict
|
||||
{
|
||||
bool escaped;
|
||||
};
|
||||
|
||||
struct show_line
|
||||
{
|
||||
struct wincurpos wcp_start;
|
||||
struct wincurpos wcp_current;
|
||||
struct winsize ws;
|
||||
int out_fd;
|
||||
char* current_line;
|
||||
size_t current_cursor;
|
||||
bool invalidated;
|
||||
};
|
||||
|
||||
struct wincurpos predict_cursor(struct cursor_predict* cursor_predict,
|
||||
struct wincurpos wcp,
|
||||
struct winsize ws,
|
||||
wchar_t c);
|
||||
bool predict_will_scroll(struct cursor_predict cursor_predict,
|
||||
struct wincurpos wcp,
|
||||
struct winsize ws,
|
||||
wchar_t c);
|
||||
void show_line_begin(struct show_line* show_state, int out_fd);
|
||||
bool show_line_is_weird(const char* line);
|
||||
void show_line_change_cursor(struct show_line* show_state, struct wincurpos wcp);
|
||||
bool show_line_optimized(struct show_line* show_state, const char* line, size_t cursor);
|
||||
void show_line(struct show_line* show_state, const char* line, size_t cursor);
|
||||
void show_line_clear(struct show_line* show_state);
|
||||
void show_line_abort(struct show_line* show_state);
|
||||
void show_line_finish(struct show_line* show_state);
|
||||
|
||||
#endif
|
37
sh/util.cpp
Normal file
37
sh/util.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*******************************************************************************
|
||||
|
||||
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
util.cpp
|
||||
Utility functions.
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
char* strdup_safe(const char* string)
|
||||
{
|
||||
return string ? strdup(string) : NULL;
|
||||
}
|
||||
|
||||
const char* getenv_safe(const char* name, const char* def)
|
||||
{
|
||||
const char* ret = getenv(name);
|
||||
return ret ? ret : def;
|
||||
}
|
29
sh/util.h
Normal file
29
sh/util.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*******************************************************************************
|
||||
|
||||
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
util.h
|
||||
Utility functions.
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
char* strdup_safe(const char* string);
|
||||
const char* getenv_safe(const char* name, const char* def = "");
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue