From 8259db44afcb53c03456a465a2e6f7077753bf0b Mon Sep 17 00:00:00 2001 From: Sumedh Pendurkar Date: Sun, 4 Dec 2016 17:04:19 +0100 Subject: [PATCH] new feature: complete a fragment to a longer word found in the buffer Executing the 'Complete' function -- bound by default to ^] -- will search from the start of the current buffer for full words that start with the word fragment that is before the cursor, and will complete this fragment to the first word that is found. Each consecutive call of 'Complete' will search for the next matching word and will complete the fragment to that. --- src/global.c | 10 ++++ src/nano.c | 7 +++ src/nano.h | 7 +++ src/proto.h | 5 ++ src/text.c | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 209 insertions(+), 1 deletion(-) 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..d83ace54 100644 --- a/src/text.c +++ b/src/text.c @@ -49,6 +49,13 @@ static filestruct *jusbottom = NULL; #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(HUSH, _("No further matches")); + refresh_needed = TRUE; + } else + statusline(ALERT, _("No matches")); + + free(shard); +} +#endif -- 2.11.0