diff --git a/src/global.c b/src/global.c index dd2e71ab..233dea71 100644 --- a/src/global.c +++ b/src/global.c @@ -59,6 +59,11 @@ int last_line_y; message_type lastmessage = HUSH; /* Messages of type HUSH should not overwrite type MILD nor ALERT. */ +#ifndef NANO_TINY +filestruct *pletion_line = NULL; + /* The line where the last completion was found, if any. */ +#endif + int controlleft, controlright, controlup, controldown; #ifndef NANO_TINY int shiftcontrolleft, shiftcontrolright, shiftcontrolup, shiftcontroldown; @@ -536,6 +541,7 @@ void shortcut_init(void) #endif const char *nano_undo_msg = N_("Undo the last operation"); const char *nano_redo_msg = N_("Redo the last undone operation"); + const char *nano_completion_msg = N_("Try and complete the current word"); #endif const char *nano_back_msg = N_("Go back one character"); const char *nano_forward_msg = N_("Go forward one character"); @@ -813,6 +819,9 @@ void shortcut_init(void) N_("Undo"), IFSCHELP(nano_undo_msg), TOGETHER, NOVIEW); add_to_funcs(do_redo, MMAIN, N_("Redo"), IFSCHELP(nano_redo_msg), BLANKAFTER, NOVIEW); + + add_to_funcs(complete_a_word, MMAIN, + N_("Complete"), IFSCHELP(nano_completion_msg), BLANKAFTER, NOVIEW); #endif /* !NANO_TINY */ add_to_funcs(do_left, MMAIN, @@ -1095,6 +1104,7 @@ void shortcut_init(void) add_to_sclist(MMAIN, "M-{", 0, do_unindent, 0); add_to_sclist(MMAIN, "M-U", 0, do_undo, 0); add_to_sclist(MMAIN, "M-E", 0, do_redo, 0); + add_to_sclist(MMAIN, "^]", 0, complete_a_word, 0); #endif #ifdef ENABLE_COMMENT add_to_sclist(MMAIN, "M-3", 0, do_comment, 0); diff --git a/src/nano.c b/src/nano.c index d64cd9b9..54f652d6 100644 --- a/src/nano.c +++ b/src/nano.c @@ -1672,6 +1672,9 @@ int do_input(bool allow_funcs) preserve = TRUE; #ifndef NANO_TINY + if (s->scfunc != complete_a_word) + pletion_line = NULL; + if (s->scfunc == do_toggle_void) { do_toggle(s->toggle); if (s->toggle != CUT_TO_END) @@ -1707,6 +1710,10 @@ int do_input(bool allow_funcs) update_line(openfile->current, openfile->current_x); } } +#ifndef NANO_TINY + else + pletion_line = NULL; +#endif /* If we aren't cutting or copying text, and the key wasn't a toggle, * blow away the text in the cutbuffer upon the next cutting action. */ diff --git a/src/nano.h b/src/nano.h index 93ec5e1b..beb6afe6 100644 --- a/src/nano.h +++ b/src/nano.h @@ -484,6 +484,13 @@ typedef struct subnfunc { /* Next item in the list. */ } subnfunc; +#ifndef NANO_TINY +typedef struct completion_word { + char *word; + struct completion_word *next; +} completion_word; +#endif + /* The elements of the interface that can be colored differently. */ enum { diff --git a/src/proto.h b/src/proto.h index 3d8e1ea4..d61467ea 100644 --- a/src/proto.h +++ b/src/proto.h @@ -47,6 +47,10 @@ extern int last_line_y; extern message_type lastmessage; +#ifndef NANO_TINY +extern filestruct *pletion_line; +#endif + extern int controlleft; extern int controlright; extern int controlup; @@ -645,6 +649,7 @@ void do_formatter(void); void do_wordlinechar_count(void); #endif void do_verbatim_input(void); +void complete_a_word(void); /* All functions in utils.c. */ void get_homedir(void); diff --git a/src/text.c b/src/text.c index a7cba86f..073f66e4 100644 --- a/src/text.c +++ b/src/text.c @@ -48,6 +48,13 @@ static filestruct *jusbottom = NULL; /* A pointer to the end of the buffer with unjustified text. */ #endif +#ifndef NANO_TINY +static int pletion_x = 0; + /* The x position in pletion_line of the last found completion. */ +static completion_word *list_of_completions; + /* A linked list of the completions that have been attepmted. */ +#endif + #ifndef NANO_TINY /* Toggle the mark. */ void do_mark(void) @@ -812,7 +819,7 @@ void do_undo(void) break; } - if (undidmsg) + if (undidmsg && !pletion_line) statusline(HUSH, _("Undid action (%s)"), undidmsg); renumber(f); @@ -3676,3 +3683,175 @@ void do_verbatim_input(void) free(output); } + +#ifndef NANO_TINY +/* Copy the found completion candidate. */ +char *copy_completion(char *check_line, int start) +{ + char *word; + int position = start, len_of_word = 0, index = 0; + + /* Find the length of the word by travelling to its end. */ + while (is_word_mbchar(&check_line[position], FALSE)) { + int next = move_mbright(check_line, position); + len_of_word += next - position; + position = next; + } + + word = (char *)nmalloc((len_of_word + 1) * sizeof(char)); + + /* Simply copy the word. */ + while (index < len_of_word) + word[index++] = check_line[start++]; + + word[index] = '\0'; + return word; +} + +/* Look at the fragment the user has typed, then search the current buffer for + * the first word that starts with this fragment, and tentatively complete the + * fragment. If the user types 'Complete' again, search and paste the next + * possible completion. */ +void complete_a_word(void) +{ + char *shard, *completion = NULL; + int start_of_shard, shard_length = 0; + int i = 0, j = 0; + completion_word *some_word; + bool was_set_wrapping = !ISSET(NO_WRAP); + + /* If this is a fresh completion attempt... */ + if (pletion_line == NULL) { + /* Clear the list of words of a previous completion run. */ + while (list_of_completions != NULL) { + completion_word *dropit = list_of_completions; + list_of_completions = list_of_completions->next; + free(dropit->word); + free(dropit); + } + + /* Prevent a completion from being merged with typed text. */ + openfile->last_action = OTHER; + + /* Initialize the starting point for searching. */ + pletion_line = openfile->fileage; + pletion_x = 0; + + /* Wipe the "No further matches" message. */ + blank_statusbar(); + wnoutrefresh(bottomwin); + } else { + /* Remove the attempted completion from the buffer. */ + do_undo(); + } + + /* Find the start of the fragment that the user typed. */ + start_of_shard = openfile->current_x; + while (start_of_shard > 0) { + int step_left = move_mbleft(openfile->current->data, start_of_shard); + + if (!is_word_mbchar(&openfile->current->data[step_left], FALSE)) + break; + start_of_shard = step_left; + } + + /* If there is no word fragment before the cursor, do nothing. */ + if (start_of_shard == openfile->current_x) { + pletion_line = NULL; + return; + } + + shard = (char *)nmalloc((openfile->current_x - start_of_shard + 1) * sizeof(char)); + + /* Copy the fragment that has to be searched for. */ + while (start_of_shard < openfile->current_x) + shard[shard_length++] = openfile->current->data[start_of_shard++]; + shard[shard_length] = '\0'; + + /* Run through all of the lines in the buffer, looking for shard. */ + while (pletion_line != NULL) { + int threshold = strlen(pletion_line->data) - shard_length - 1; + /* The point where we can stop searching for shard. */ + + /* Traverse the whole line, looking for shard. */ + for (i = pletion_x; i < threshold; i++) { + /* If the first byte doesn't match, run on. */ + if (pletion_line->data[i] != shard[0]) + continue; + + /* Compare the rest of the bytes in shard. */ + for (j = 1; j < shard_length; j++) + if (pletion_line->data[i + j] != shard[j]) + break; + + /* If not all of the bytes matched, continue searching. */ + if (j < shard_length) + continue; + + /* If the found match is not /longer/ than shard, skip it. */ + if (!is_word_mbchar(&pletion_line->data[i + j], FALSE)) + continue; + + /* If the match is not a separate word, skip it. */ + if (i > 0 && is_word_mbchar(&pletion_line->data[ + move_mbleft(pletion_line->data, i)], FALSE)) + continue; + + /* If this match is the shard itself, ignore it. */ + if (pletion_line == openfile->current && + i == openfile->current_x - shard_length) + continue; + + completion = copy_completion(pletion_line->data, i); + + /* Look among earlier attempted completions for a duplicate. */ + some_word = list_of_completions; + while (some_word && strcmp(some_word->word, completion) != 0) + some_word = some_word->next; + + /* If we've already tried this word, skip it. */ + if (some_word != NULL) { + free(completion); + continue; + } + + /* Add the found word to the list of completions. */ + some_word = (completion_word *)nmalloc(sizeof(completion_word)); + some_word->word = completion; + some_word->next = list_of_completions; + list_of_completions = some_word; + + /* Temporarily disable wrapping so only one undo item is added. */ + SET(NO_WRAP); + + /* Inject the completion into the buffer. */ + do_output(&completion[shard_length], + strlen(completion) - shard_length, FALSE); + + /* If needed, reenable wrapping and wrap the current line. */ + if (was_set_wrapping) { + UNSET(NO_WRAP); + do_wrap(openfile->current); + } + + /* Set the position for a possible next search attempt. */ + pletion_x = ++i; + + free(shard); + return; + } + + pletion_line = pletion_line->next; + pletion_x = 0; + } + + /* The search has reached the end of the file. */ + if (list_of_completions != NULL) { + statusline(ALERT, _("No further matches")); + refresh_needed = TRUE; + } else + statusline(ALERT, _("No matches")); + + free(shard); +} +#endif