VitaShell/text.c
2018-09-13 14:46:10 +02:00

1035 lines
28 KiB
C

/*
VitaShell
Copyright (C) 2015-2018, TheFloW
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/>.
*/
#include "main.h"
#include "browser.h"
#include "context_menu.h"
#include "archive.h"
#include "file.h"
#include "text.h"
#include "hex.h"
#include "theme.h"
#include "utils.h"
#include "language.h"
#include "ime_dialog.h"
#include "message_dialog.h"
enum TextMenuEntrys {
TEXT_MENU_ENTRY_MARK_UNMARK_ALL,
TEXT_MENU_ENTRY_CUT,
TEXT_MENU_ENTRY_COPY,
TEXT_MENU_ENTRY_PASTE,
TEXT_MENU_ENTRY_DELETE,
TEXT_MENU_ENTRY_INSERT_EMPTY_LINE,
TEXT_MENU_ENTRY_SEARCH,
TEXT_MENU_ENTRY_HEX_EDITOR,
};
MenuEntry text_menu_entries[] = {
{ UNMARK_ALL, 0, 0, CTX_INVISIBLE },
{ CUT, 2, 0, CTX_INVISIBLE },
{ COPY, 3, 0, CTX_INVISIBLE },
{ PASTE, 4, 0, CTX_INVISIBLE },
{ DELETE, 6, 0, CTX_VISIBLE },
{ INSERT_EMPTY_LINE, 7, 0, CTX_VISIBLE },
{ SEARCH, 9, 0, CTX_VISIBLE },
{ OPEN_HEX_EDITOR, 11, 0, CTX_VISIBLE },
};
#define N_TEXT_MENU_ENTRIES (sizeof(text_menu_entries) / sizeof(MenuEntry))
static int contextMenuEnterCallback(int pos, void *context);
static ContextMenu context_menu_text = {
.parent = NULL,
.entries = text_menu_entries,
.n_entries = N_TEXT_MENU_ENTRIES,
.max_width = 0.0f,
.callback = contextMenuEnterCallback,
.sel = -1,
};
typedef struct TextEditorState {
int running;
char *buffer;
int size;
int base_pos;
int rel_pos;
int offset_list[MAX_LINES];
int selection_list[MAX_SELECTION];
int n_selections;
int n_copied_lines;
int copy_reset;
int modify_allowed;
CopyEntry copy_buffer[MAX_COPY_BUFFER_SIZE];
TextList list;
int changed;
int edit_line;
char search_term[MAX_LINE_CHARACTERS];
int search_result_offsets[MAX_SEARCH_RESULTS];
int search_term_input;
int n_search_results;
int search_thid;
int count_lines_thid;
int hex_viewer;
int count_lines_running;
int n_lines;
int search_running;
} TextEditorState;
typedef struct SearchParams {
TextEditorState *state;
char search_term[MAX_LINE_CHARACTERS];
} SearchParams;
typedef struct CountParams {
TextEditorState *state;
} CountParams;
void initTextContextMenuWidth() {
int i;
for (i = 0; i < N_TEXT_MENU_ENTRIES; i++) {
context_menu_text.max_width = MAX(context_menu_text.max_width, pgf_text_width(language_container[text_menu_entries[i].name]));
}
context_menu_text.max_width += 2.0f * CONTEXT_MENU_MARGIN;
context_menu_text.max_width = MAX(context_menu_text.max_width, CONTEXT_MENU_MIN_WIDTH);
}
static void textListAddEntry(TextList *list, TextListEntry *entry) {
entry->next = NULL;
entry->previous = NULL;
if (list->head == NULL) {
list->head = entry;
list->tail = entry;
} else {
TextListEntry *tail = list->tail;
tail->next = entry;
entry->previous = tail;
list->tail = entry;
}
list->length++;
}
static void textListEmpty(TextList *list) {
TextListEntry *entry = list->head;
while (entry) {
TextListEntry *next = entry->next;
free(entry);
entry = next;
}
list->head = NULL;
list->tail = NULL;
list->length = 0;
}
#define TAB_SIZE 4
static int textReadLine(char *buffer, int offset, int size, char *line) {
// Get line
int line_width = 0;
int count = 0;
int i;
for (i = 0; i < MIN(size, MIN(size - offset, MAX_LINE_CHARACTERS - 1)); i++) {
char ch = buffer[offset + i];
char ch_width = 0;
// Line break
if (ch == '\n') {
i++; // Skip it
break;
}
// Tab
if (ch == '\t') {
ch_width = TAB_SIZE * font_size_cache[' '];
} else {
ch_width = font_size_cache[(int)ch];
if (ch_width == 0) {
ch = ' '; // Change invalid characters to space
ch_width = font_size_cache[(int)ch];
}
}
// Too long
if ((line_width + ch_width) >= (MAX_WIDTH - TEXT_START_X + SHELL_MARGIN_X))
break;
// Increase line width
line_width += ch_width;
// Add to line string
if (line)
line[count++] = ch;
}
// End of line
if (line)
line[count] = '\0';
return i;
}
static void updateTextEntry(TextEditorState *state, TextListEntry* entry, int rel_pos) {
entry->line_number = state->base_pos + rel_pos;
// Mark entry as selected
entry->selected = 0;
int j = 0;
for (j = 0; j < state->n_selections; j++) {
if (entry->line_number == state->selection_list[j]) {
entry->selected = 1;
break;
}
}
int length = textReadLine(state->buffer, state->offset_list[state->base_pos + rel_pos], state->size, entry->line);
state->offset_list[state->base_pos + rel_pos + 1] = state->offset_list[state->base_pos + rel_pos] + length;
}
static void updateTextEntries(TextEditorState *state) {
TextListEntry *entry = state->list.head;
int i;
for (i = 0; i < MAX_ENTRIES; i++) {
if (!entry) {
break;
}
updateTextEntry(state, entry, i);
entry = entry->next;
}
}
static CopyEntry *copy_line(TextEditorState *state, int line_number) {
if (state->copy_reset) {
state->copy_reset = 0;
state->n_copied_lines = 0;
}
// Get current line
int line_start = state->offset_list[line_number];
char line[MAX_LINE_CHARACTERS];
int length = textReadLine(state->buffer, line_start, state->size, line);
CopyEntry *entry = &state->copy_buffer[state->n_copied_lines];
// Copy line into copy_buffer
memcpy(entry->line, &state->buffer[line_start], length);
// Make sure line end with a newline
if (entry->line[length - 1] != '\n') {
entry->line[length] = '\n';
length++;
}
// Terminate line
state->copy_buffer[state->n_copied_lines].line[length] = '\0';
state->n_copied_lines++;
return entry;
}
static void delete_line(TextEditorState *state, int line_number) {
// Get current line
int line_start = state->offset_list[line_number];
char line[MAX_LINE_CHARACTERS];
int length = textReadLine(state->buffer, line_start, state->size, line);
// Remove line
memmove(&state->buffer[line_start], &state->buffer[line_start + length], state->size - line_start);
state->size -= length;
state->n_lines -= 1;
// Add empty line if resulting buffer is empty
if (state->size == 0) {
state->size = 1;
state->n_lines = 1;
state->buffer[0] = '\n';
}
if (state->base_pos + state->rel_pos >= state->n_lines) {
state->rel_pos = state->n_lines - state->base_pos - 1;
}
if (state->rel_pos < 0) {
state->base_pos += state->rel_pos;
state->rel_pos = 0;
}
state->changed = 1;
state->n_selections = 0;
// Update entries
updateTextEntries(state);
}
static void insert_line(TextEditorState *state, char *line, int pos) {
int offset = state->offset_list[pos];
// calculated size of inserted line
int length = strlen(line);
// Make space for inserted line
memmove(&state->buffer[offset + length], &state->buffer[offset], state->size - offset);
state->size += length;
// Insert the lines
memcpy(&state->buffer[offset], line, length);
int i;
for (i = 0; i < length; i++) {
if (line[i] == '\n') {
state->n_lines++;
}
}
state->n_selections = 0;
state->changed = 1;
state->copy_reset = 1;
// Update entries
updateTextEntries(state);
}
static void cut_line(TextEditorState *state, int line_number) {
copy_line(state, line_number);
delete_line(state, line_number);
}
static void paste_lines(TextEditorState *state, int pos) {
int line_start = state->offset_list[pos];
// calculated size of pasted content
int length = 0, i;
for (i = 0; i < state->n_copied_lines; i++) {
length += strlen(state->copy_buffer[i].line);
}
// Make space for pasted lines
memmove(&state->buffer[line_start + length], &state->buffer[line_start], state->size - line_start);
state->size += length;
// Paste the lines
for (i = 0; i < state->n_copied_lines; i++) {
int line_length = strlen(state->copy_buffer[i].line);
memcpy(&state->buffer[line_start], state->copy_buffer[i].line, line_length);
line_start += line_length;
}
state->n_lines += state->n_copied_lines;
state->changed = 1;
state->copy_reset = 1;
state->n_selections = 0;
// Update entries
updateTextEntries(state);
}
static int cmp (const void * a, const void * b) {
return ( *(int*)a - *(int*)b );
}
static int contextMenuEnterCallback(int sel, void *context) {
TextEditorState *state = (TextEditorState*) context;
switch (sel) {
case TEXT_MENU_ENTRY_SEARCH:
initImeDialog(language_container[ENTER_SEARCH_TERM], "", MAX_LINE_CHARACTERS, SCE_IME_TYPE_DEFAULT, 0, 0);
state->search_term_input = 1;
break;
case TEXT_MENU_ENTRY_HEX_EDITOR:
state->hex_viewer = 1;
if (state->changed) {
initMessageDialog(SCE_MSG_DIALOG_BUTTON_TYPE_YESNO, language_container[SAVE_MODIFICATIONS]);
}
break;
case TEXT_MENU_ENTRY_COPY:
state->n_copied_lines = 0;
// Sort the selection list
qsort(state->selection_list, state->n_selections, sizeof(int), cmp);
int i;
for (i = 0; i < state->n_selections; i++) {
copy_line(state, state->selection_list[i]);
}
state->n_selections = 0;
break;
case TEXT_MENU_ENTRY_CUT:
state->n_copied_lines = 0;
// Sort the selection list
qsort(state->selection_list, state->n_selections, sizeof(int), cmp);
// Cut the lines in reversed order to not break the offsets
for (i = state->n_selections - 1; i >= 0; i--) {
cut_line(state, state->selection_list[i]);
}
// Reverse the order of the copied lines
int j;
for (i = 0, j = state->n_selections - 1; i < j; i++, j--) {
CopyEntry tmp = state->copy_buffer[i];
state->copy_buffer[j] = state->copy_buffer[i];
state->copy_buffer[i] = tmp;
}
state->n_selections = 0;
break;
case TEXT_MENU_ENTRY_PASTE:
paste_lines(state, state->base_pos + state->rel_pos + 1);
break;
case TEXT_MENU_ENTRY_DELETE:
delete_line(state, state->base_pos + state->rel_pos);
break;
case TEXT_MENU_ENTRY_INSERT_EMPTY_LINE:
insert_line(state, "\n", state->base_pos + state->rel_pos + 1);
break;
case TEXT_MENU_ENTRY_MARK_UNMARK_ALL:
state->n_selections = 0;
updateTextEntries(state);
break;
}
return CONTEXT_MENU_CLOSING;
}
static void setContextMenuVisibilities(TextEditorState *state) {
// Cut & Copy & Unmark only visible when at least one line is selected
text_menu_entries[TEXT_MENU_ENTRY_MARK_UNMARK_ALL].visibility = state->n_selections == 0 ? CTX_INVISIBLE : CTX_VISIBLE;
text_menu_entries[TEXT_MENU_ENTRY_CUT].visibility = state->n_selections == 0 ? CTX_INVISIBLE : CTX_VISIBLE;
text_menu_entries[TEXT_MENU_ENTRY_COPY].visibility = state->n_selections == 0 ? CTX_INVISIBLE : CTX_VISIBLE;
// Paste only visible when at least one line is in copy buffer
text_menu_entries[TEXT_MENU_ENTRY_PASTE].visibility = state->n_copied_lines == 0 ? CTX_INVISIBLE : CTX_VISIBLE;
// Go to first entry
int i;
for (i = 0; i < N_TEXT_MENU_ENTRIES; i++) {
if (text_menu_entries[i].visibility == CTX_VISIBLE) {
context_menu_text.sel = i;
break;
}
}
if (i == N_TEXT_MENU_ENTRIES)
context_menu_text.sel = -1;
}
static int count_lines_thread(SceSize args, CountParams *params) {
TextEditorState *state = params->state;
state->count_lines_running = 1;
state->n_lines = 0;
int offset = 0;
while (state->count_lines_running && offset < state->size && state->n_lines < MAX_LINES) {
offset += textReadLine(state->buffer, offset, state->size, NULL);
state->n_lines++;
sceKernelDelayThread(1000);
}
return sceKernelExitDeleteThread(0);
}
static int search_thread(SceSize args, SearchParams *argp) {
TextEditorState *state = argp->state;
char *search_term = argp->search_term;
int search_term_length = strlen(search_term);
int *search_result_offsets = state->search_result_offsets;
state->search_running = 1;
state->n_search_results = 0;
int offset = 0;
// make sure buffer is null-terminated
state->buffer[state->size] = '\0';
char *r;
while (state->search_running && offset < state->size && state->n_search_results < MAX_SEARCH_RESULTS) {
r = strcasestr(state->buffer+offset, search_term);
if (r == NULL) {
state->search_running = 0;
continue;
}
int index = r - state->buffer;
search_result_offsets[state->n_search_results++] = index;
offset = index + 1;
sceKernelDelayThread(1000);
}
state->search_running = 0;
return sceKernelExitDeleteThread(0);
}
int textViewer(const char *file) {
TextEditorState *s = malloc(sizeof(TextEditorState));
if (!s)
return VITASHELL_ERROR_NO_MEMORY;
char *buffer_base = memalign(4096, BIG_BUFFER_SIZE);
if (!buffer_base)
return VITASHELL_ERROR_NO_MEMORY;
s->running = 1;
s->hex_viewer = 0;
s->n_copied_lines = 0;
s->copy_reset = 0;
s->modify_allowed = 1;
s->offset_list[0] = 0;
s->count_lines_running = 0;
s->n_lines = 0;
s->search_running = 0;
s->edit_line = -1;
if (isInArchive()) {
s->size = ReadArchiveFile(file, buffer_base, BIG_BUFFER_SIZE);
s->modify_allowed = 0;
} else {
s->size = ReadFile(file, buffer_base, BIG_BUFFER_SIZE);
}
if (s->size < 0) {
free(buffer_base);
return s->size;
}
s->buffer = buffer_base;
int has_utf8_bom = 0;
char utf8_bom[3] = {0xEF, 0xBB, 0xBF};
if (s->size >= 3 && memcmp(buffer_base, utf8_bom, 3) == 0) {
s->buffer += 3;
has_utf8_bom = 1;
s->size -= 3;
}
if (s->size == 0) {
s->size = 1;
s->buffer[0] = '\n';
}
if (s->buffer[s->size - 1] != '\n') {
s->buffer[s->size++] = '\n';
}
s->base_pos = 0;
s->rel_pos = 0;
s->n_selections = 0;
memset(&s->list, 0, sizeof(TextList));
// Init context menu param
context_menu_text.context = s;
int i;
for (i = 0; i < MAX_ENTRIES; i++) {
TextListEntry *entry = malloc(sizeof(TextListEntry));
entry->line_number = i;
entry->selected = 0;
int length = textReadLine(s->buffer, s->offset_list[i], s->size, entry->line);
s->offset_list[i + 1] = s->offset_list[i] + length;
textListAddEntry(&s->list, entry);
}
CountParams count_params;
count_params.state = s;
s->count_lines_thid = sceKernelCreateThread("count_lines_thread", (SceKernelThreadEntry)count_lines_thread, 0x10000100, 0x10000, 0, 0x70000, NULL);
if (s->count_lines_thid >= 0)
sceKernelStartThread(s->count_lines_thid, sizeof(CountParams), &count_params);
s->edit_line = -1;
s->changed = 0;
s->search_term_input = 0;
s->search_thid = 0;
s->n_search_results = 0;
while (s->running) {
readPad();
if (!isImeDialogRunning() && !isMessageDialogRunning()) {
if (getContextMenuMode() == CONTEXT_MENU_CLOSED && s->hex_viewer == 1)
break;
if (getContextMenuMode() != CONTEXT_MENU_CLOSED) {
contextMenuCtrl();
} else {
// Context menu trigger
if (pressed_pad[PAD_TRIANGLE]) {
if (getContextMenuMode() == CONTEXT_MENU_CLOSED) {
setContextMenu(&context_menu_text);
setContextMenuVisibilities(s);
setContextMenuMode(CONTEXT_MENU_OPENING);
}
}
if (hold_pad[PAD_UP] || hold2_pad[PAD_LEFT_ANALOG_UP]) {
if (s->rel_pos > 0) {
s->rel_pos--;
} else {
if (s->base_pos > 0) {
s->base_pos--;
// Tail to head
s->list.tail->next = s->list.head;
s->list.head->previous = s->list.tail;
s->list.head = s->list.tail;
// Second last to tail
s->list.tail = s->list.tail->previous;
s->list.tail->next = NULL;
// No previous
s->list.head->previous = NULL;
// Update line_number
s->list.head->line_number = s->base_pos;
// Read
textReadLine(s->buffer, s->offset_list[s->base_pos], s->size, s->list.head->line);
// Update the entry
updateTextEntry(s, s->list.head, 0);
}
}
s->copy_reset = 1;
} else if (hold_pad[PAD_DOWN] || hold2_pad[PAD_LEFT_ANALOG_DOWN]) {
if (s->offset_list[s->rel_pos + 1] < s->size) {
if ((s->rel_pos + 1) < MAX_POSITION) {
if (s->base_pos + s->rel_pos < s->n_lines - 1)
s->rel_pos++;
} else {
if (s->offset_list[s->base_pos + s->rel_pos + 1] < s->size) {
s->base_pos++;
// Head to tail
s->list.head->previous = s->list.tail;
s->list.tail->next = s->list.head;
s->list.tail = s->list.head;
// Second first to head
s->list.head = s->list.head->next;
s->list.head->previous = NULL;
// No next
s->list.tail->next = NULL;
// Update line_number
s->list.tail->line_number = s->base_pos + MAX_ENTRIES - 1;
// Read
int length = textReadLine(s->buffer, s->offset_list[s->base_pos + MAX_ENTRIES - 1], s->size, s->list.tail->line);
s->offset_list[s->base_pos + MAX_ENTRIES] = s->offset_list[s->base_pos + MAX_ENTRIES - 1] + length;
// Update the entry
updateTextEntry(s, s->list.tail, MAX_ENTRIES - 1);
}
}
}
s->copy_reset = 1;
}
if (s->n_search_results > 0) {
TextListEntry *entry = s->list.head;
int i;
for (i = 0; i < s->rel_pos; i++)
entry = entry->next;
int entry_start_offset = s->offset_list[entry->line_number];
int entry_end_offset = s->offset_list[entry->line_number + 1];
int target_offset = 0;
// Skip to next search result
if (pressed_pad[PAD_RTRIGGER]) {
for (i = 0; i < s->n_search_results; i++) {
if (s->search_result_offsets[i] > entry_end_offset) {
target_offset = s->search_result_offsets[i] - entry_start_offset;
break;
}
}
} // Skip to next last result
else if (pressed_pad[PAD_LTRIGGER]) {
for (i = s->n_search_results - 1; i >= 0; i--) {
if (s->search_result_offsets[i] < entry_start_offset) {
target_offset = s->search_result_offsets[i] - entry_start_offset;
break;
}
}
}
if (target_offset != 0) {
int dir = target_offset > 0 ? 1 : -1;
int line = s->base_pos + s->rel_pos;
int offset = s->offset_list[line];
while (offset < s->size && offset >= 0 && target_offset != 0) {
offset += dir;
target_offset -= dir;
if (s->buffer[offset] == '\n') {
line += dir;
if (dir > 0) {
s->offset_list[line] = offset + 1;
} else {
s->offset_list[line + 1] = offset + 1;
}
}
}
if (target_offset == 0) {
s->base_pos = line;
s->rel_pos = 0;
updateTextEntries(s);
}
}
} else {
// Page skip
if (hold_pad[PAD_LTRIGGER] || hold_pad[PAD_RTRIGGER]) {
if (hold_pad[PAD_LTRIGGER]) { // Skip page up
s->base_pos = s->base_pos - MAX_ENTRIES;
if (s->base_pos < 0) {
s->base_pos = 0;
s->rel_pos = 0;
}
} else { // Skip page down
s->base_pos = s->base_pos + MAX_ENTRIES;
if (s->base_pos >= s->n_lines - MAX_POSITION) {
s->base_pos = MAX(s->n_lines - MAX_POSITION, 0);
s->rel_pos = MIN(MAX_POSITION - 1, s->n_lines - 1);
}
}
// Update entries
updateTextEntries(s);
}
}
// buffer modifying actions
if (s->modify_allowed && !s->search_running) {
if(s->edit_line <= 0 && pressed_pad[PAD_ENTER]) {
int line_start = s->offset_list[s->base_pos + s->rel_pos];
char line[MAX_LINE_CHARACTERS];
textReadLine(s->buffer, line_start, s->size, line);
initImeDialog(language_container[EDIT_LINE], line, MAX_LINE_CHARACTERS, SCE_IME_TYPE_DEFAULT, SCE_IME_OPTION_MULTILINE, 0);
s->edit_line = s->base_pos + s->rel_pos;
}
// Delete line
if (pressed_pad[PAD_LEFT] && s->n_copied_lines < MAX_COPY_BUFFER_SIZE) {
delete_line(s, s->base_pos + s->rel_pos);
}
// Insert new line
if (pressed_pad[PAD_RIGHT]) {
insert_line(s, "\n", s->base_pos + s->rel_pos + 1);
}
}
// Cancel
if (pressed_pad[PAD_CANCEL]) {
if (s->n_search_results) {
s->n_search_results = 0;
} else {
if (s->changed) {
initMessageDialog(SCE_MSG_DIALOG_BUTTON_TYPE_YESNO, language_container[SAVE_MODIFICATIONS]);
} else {
s->hex_viewer = 0;
break;
}
}
}
// (De-)select current line
if (pressed_pad[PAD_SQUARE]) {
int cur_line = s->base_pos + s->rel_pos;
int line_selected = 1;
int i;
for (i = 0; i < s->n_selections; i++) {
if (s->selection_list[i] == cur_line) {
line_selected = 0;
// Remove current line from selections
s->selection_list[i] = s->selection_list[--s->n_selections];
break;
}
}
if (line_selected) {
// Add current line to selections
s->selection_list[s->n_selections++] = cur_line;
}
TextListEntry *entry = s->list.head;
for (i = 0; i < s->rel_pos; i++)
entry = entry->next;
entry->selected = line_selected;
}
}
} else {
int msg_result = updateMessageDialog();
if (msg_result == MESSAGE_DIALOG_RESULT_YES) {
SceUID fd = sceIoOpen(file, SCE_O_WRONLY | SCE_O_TRUNC, 0777);
if (fd >= 0) {
sceIoWrite(fd, buffer_base, has_utf8_bom ? s->size + sizeof(utf8_bom) : s->size);
sceIoClose(fd);
}
break;
} else if (msg_result == MESSAGE_DIALOG_RESULT_NO) {
break;
}
int ime_result = updateImeDialog();
if (s->search_term_input) {
if (ime_result == IME_DIALOG_RESULT_FINISHED) {
char *search_term = (char *)getImeDialogInputTextUTF8();
int length = strlen(search_term);
if (length >= MIN_SEARCH_TERM_LENGTH) {
// kill old search if it is already running
if (s->search_running) {
s->search_running = 0;
sceKernelWaitThreadEnd(s->search_thid, NULL, NULL);
}
SearchParams search_params;
search_params.state = s;
strcpy(search_params.search_term, search_term);
strcpy(s->search_term, search_term);
s->search_thid = sceKernelCreateThread("search_thread", (SceKernelThreadEntry)search_thread, 0x10000100, 0x10000, 0, 0x70000, NULL);
if (s->search_thid >= 0)
sceKernelStartThread(s->search_thid, sizeof(SearchParams), &search_params);
}
s->search_term_input = 0;
} else if (ime_result == IME_DIALOG_RESULT_CANCELED) {
s->search_term_input = 0;
}
}
if (s->edit_line >= 0) {
if (ime_result == IME_DIALOG_RESULT_FINISHED) {
int line_start = s->offset_list[s->edit_line];
char line[MAX_LINE_CHARACTERS];
int length = textReadLine(s->buffer, line_start, s->size, line);
// Don't count newline
if (s->buffer[line_start + length - 1] == '\n') {
length--;
}
char *new_line = (char *)getImeDialogInputTextUTF8();
int new_length = strlen(new_line);
// Move data if size has changed
if (new_length != length) {
memmove(&s->buffer[line_start + new_length], &s->buffer[line_start + length], s->size - line_start - length);
s->size += (new_length-length);
}
// Copy new line into buffer
memcpy(&s->buffer[line_start], new_line, new_length);
// Add new lines to n_lines
int i;
for (i = 0; i < new_length; i++) {
if (new_line[i] == '\n') {
s->n_lines++;
}
}
// Update entries
updateTextEntries(s);
s->edit_line = -1;
s->changed = 1;
} else if (ime_result == IME_DIALOG_RESULT_CANCELED) {
s->edit_line = -1;
}
}
}
// Start drawing
startDrawing(bg_text_image);
// Draw shell info
drawShellInfo(file);
// Draw scroll bar
drawScrollBar(s->base_pos, s->n_lines);
// Text
TextListEntry *entry = s->list.head;
int i;
for (i = 0; i < s->list.length; i++) {
char *line = entry->line;
int line_lenght = strlen(line);
int search_result_on_line = 0;
int entry_start_offset = s->offset_list[entry->line_number];
int entry_end_offset = entry_start_offset + line_lenght;
if (s->n_search_results > 0) {
int j;
for (j = 0; j < s->n_search_results; j++) {
int search_offset = s->search_result_offsets[j];
if (entry_start_offset <= search_offset && entry_end_offset >= search_offset) {
search_result_on_line = 1;
}
}
}
if (entry->line_number < s->n_lines) {
char line_str[5];
snprintf(line_str, 5, "%04i", entry->line_number);
int color = (s->rel_pos == i) ? TEXT_LINE_NUMBER_COLOR_FOCUS : TEXT_LINE_NUMBER_COLOR;
pgf_draw_text(SHELL_MARGIN_X, START_Y + (i * FONT_Y_SPACE), color, line_str);
}
float x = TEXT_START_X;
if (entry->selected) {
vita2d_draw_rectangle(x, START_Y + (i * FONT_Y_SPACE) + 3.0f, MAX_WIDTH - TEXT_START_X + SHELL_MARGIN_X, FONT_Y_SPACE, MARKED_COLOR);
}
while (*line) {
char *p = strchr(line, '\t');
if (p)
*p = '\0';
char *search_highlight = NULL;
if (search_result_on_line) {
search_highlight = strcasestr(line, s->search_term);
}
char tmp = '\0';
if (search_highlight) {
tmp = *search_highlight;
*search_highlight = '\0';
}
int width = pgf_draw_text(x, START_Y + (i * FONT_Y_SPACE), (s->rel_pos == i) ? TEXT_FOCUS_COLOR : TEXT_COLOR, line);
line += strlen(line);
if (p) {
*p = '\t';
x += width + TAB_SIZE * font_size_cache[' '];
line++;
}
if (search_highlight) {
*search_highlight = tmp;
int search_term_length = strlen(s->search_term);
tmp = search_highlight[search_term_length];
search_highlight[search_term_length] = '\0';
x += width;
x += pgf_draw_text(x, START_Y + (i * FONT_Y_SPACE), TEXT_HIGHLIGHT_COLOR, line);
search_highlight[search_term_length] = tmp;
line += strlen(s->search_term);
}
}
entry = entry->next;
}
// Draw context menu
drawContextMenu();
// End drawing
endDrawing();
}
s->count_lines_running = 0;
sceKernelWaitThreadEnd(s->count_lines_thid, NULL, NULL);
if (s->search_running) {
s->search_running = 0;
sceKernelWaitThreadEnd(s->search_thid, NULL, NULL);
}
textListEmpty(&s->list);
int hex_viewer = s->hex_viewer;
free(s);
free(buffer_base);
if (hex_viewer)
hexViewer(file);
return 0;
}