mc/edit/editcmd_dialogs.c
dborca fdbd5b8a86 Ticket #1486: Editor completion is confusing
Editor completion became really confusing in 4.7.0-pre1:

1. The list is populated with words from the entire file. This is arguably
better than the old style, which scanned only up to the cursor. But the number
of suggestions are limited, and if you have a bigger file, some words won't
make it in the list. Besides, I somehow liked the old way.

2. Suggestions do not end on a word boundary. This may or may be not useful; but
it just increases the list of suggestions, which slows the user. In the end, it
beats the purpose of completions.

3. It completes the word I am currently editing. That's smart. :)

4. The suggestions are displayed in the order in which they were found from the
beginning of the file. This is my main gripe, because when using the editor for
coding, it is better to have suggestions based on the distance to the cursor.
Closer means higher. When writing a function, I am mainly interested in the
variable names I recently used.

5. Sometimes, it does not work at all, but I haven't investigated the bug. :(

Signed-off-by: Slava Zanko <slavazanko@gmail.com>
2009-09-23 16:13:36 +03:00

548 lines
18 KiB
C

/*
Editor dialogs for high level editing commands
Copyright (C) 2009 The Free Software Foundation, Inc.
Written by:
Slava Zanko <slavazanko@gmail.com>, 2009.
This file is part of the Midnight Commander.
The Midnight Commander 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 2 of the
License, or (at your option) any later version.
The Midnight Commander 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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
*/
#include <config.h>
#include "../src/global.h"
#include "../src/tty/tty.h"
#include "../src/skin/skin.h" /* INPUT_COLOR */
#include "../src/tty/key.h"
#include "../src/search/search.h"
#include "../src/strutil.h"
#include "../src/widget.h"
#include "../src/wtools.h"
#include "../src/dialog.h" /* do_refresh() */
#include "../src/main.h"
#include "../src/history.h"
#include "../edit/edit-widget.h"
#include "../edit/etags.h"
#include "../edit/editcmd_dialogs.h"
/*** global variables **************************************************/
/*** file scope macro definitions **************************************/
#define SEARCH_DLG_WIDTH 58
#define SEARCH_DLG_MIN_HEIGHT 13
#define SEARCH_DLG_HEIGHT_SUPPLY 3
#define REPLACE_DLG_WIDTH 58
#define REPLACE_DLG_MIN_HEIGHT 17
#define REPLACE_DLG_HEIGHT_SUPPLY 5
/*** file scope type declarations **************************************/
/*** file scope variables **********************************************/
/*** file scope functions **********************************************/
static cb_ret_t
editcmd_dialog_raw_key_query_cb (struct Dlg_head *h, dlg_msg_t msg, int parm)
{
switch (msg) {
case DLG_KEY:
dlg_stop (h);
h->ret_value = parm;
return MSG_HANDLED;
default:
return default_dlg_callback (h, msg, parm);
}
}
/*** public functions **************************************************/
void
editcmd_dialog_replace_show (WEdit * edit, const char *search_default, const char *replace_default,
/*@out@ */ char **search_text, /*@out@ */ char **replace_text)
{
if (*search_default == '\0')
search_default = INPUT_LAST_TEXT;
{
gchar **list_of_types = mc_search_get_types_strings_array();
int REPLACE_DLG_HEIGHT = REPLACE_DLG_MIN_HEIGHT + g_strv_length (list_of_types) - REPLACE_DLG_HEIGHT_SUPPLY;
QuickWidget quick_widgets[] =
{
/* 0 */ QUICK_BUTTON (6, 10, 13, REPLACE_DLG_HEIGHT, N_("&Cancel"), B_CANCEL, NULL),
/* 1 */ QUICK_BUTTON (2, 10, 13, REPLACE_DLG_HEIGHT, N_("&OK"), B_ENTER, NULL),
#ifdef HAVE_CHARSET
/* 2 */ QUICK_CHECKBOX (33, REPLACE_DLG_WIDTH, 11, REPLACE_DLG_HEIGHT, N_("All charsets"), &edit->all_codepages),
#endif
/* 3 */ QUICK_CHECKBOX (33, REPLACE_DLG_WIDTH, 10, REPLACE_DLG_HEIGHT, N_("&Whole words"), &edit->whole_words),
/* 4 */ QUICK_CHECKBOX (33, REPLACE_DLG_WIDTH, 9, REPLACE_DLG_HEIGHT, N_("In se&lection"), &edit->only_in_selection),
/* 5 */ QUICK_CHECKBOX (33, REPLACE_DLG_WIDTH, 8, REPLACE_DLG_HEIGHT, N_("&Backwards"), &edit->replace_backwards),
/* 6 */ QUICK_CHECKBOX (33, REPLACE_DLG_WIDTH, 7, REPLACE_DLG_HEIGHT, N_("case &Sensitive"), &edit->replace_case),
/* 7 */ QUICK_RADIO (3, REPLACE_DLG_WIDTH, 7, REPLACE_DLG_HEIGHT,
g_strv_length (list_of_types), (const char **) list_of_types, &edit->search_type),
/* 8 */ QUICK_LABEL (2, REPLACE_DLG_WIDTH, 4, REPLACE_DLG_HEIGHT, N_(" Enter replacement string:")),
/* 9 */ QUICK_INPUT (3, REPLACE_DLG_WIDTH, 5, REPLACE_DLG_HEIGHT,
replace_default, REPLACE_DLG_WIDTH - 6, 0, "replace", replace_text),
/* 10 */ QUICK_LABEL (2, REPLACE_DLG_WIDTH, 2, REPLACE_DLG_HEIGHT, N_(" Enter search string:")),
/* 11 */ QUICK_INPUT (3, REPLACE_DLG_WIDTH, 3, REPLACE_DLG_HEIGHT,
search_default, REPLACE_DLG_WIDTH - 6, 0, MC_HISTORY_SHARED_SEARCH, search_text),
QUICK_END
};
QuickDialog Quick_input =
{
REPLACE_DLG_WIDTH, REPLACE_DLG_HEIGHT, -1, -1, N_(" Replace "),
"[Input Line Keys]", quick_widgets, FALSE
};
if (quick_dialog (&Quick_input) != B_CANCEL) {
edit->replace_mode = 0;
} else {
*replace_text = NULL;
*search_text = NULL;
}
g_strfreev (list_of_types);
}
}
/* --------------------------------------------------------------------------------------------- */
void
editcmd_dialog_search_show (WEdit * edit, char **search_text)
{
if (*search_text == '\0')
*search_text = INPUT_LAST_TEXT;
{
int i;
gchar **list_of_types = mc_search_get_types_strings_array();
int SEARCH_DLG_HEIGHT = SEARCH_DLG_MIN_HEIGHT + g_strv_length (list_of_types) - SEARCH_DLG_HEIGHT_SUPPLY;
int dialog_result;
QuickWidget quick_widgets[] =
{
/* 0 */
QUICK_BUTTON (6, 10, 11, SEARCH_DLG_HEIGHT, N_("&Cancel"), B_CANCEL, NULL),
/* 1 */
QUICK_BUTTON (4, 10, 11, SEARCH_DLG_HEIGHT, N_("&Find all"), B_USER, NULL),
/* 2 */
QUICK_BUTTON (2, 10, 11, SEARCH_DLG_HEIGHT, N_("&OK"), B_ENTER, NULL),
#ifdef HAVE_CHARSET
/* 3 */
QUICK_CHECKBOX (33, SEARCH_DLG_WIDTH, 9, SEARCH_DLG_HEIGHT, N_("All charsets"), &edit->all_codepages),
#endif
/* 4 */
QUICK_CHECKBOX (33, SEARCH_DLG_WIDTH, 8, SEARCH_DLG_HEIGHT, N_("&Whole words"), &edit->whole_words),
/* 5 */
QUICK_CHECKBOX (33, SEARCH_DLG_WIDTH, 7, SEARCH_DLG_HEIGHT, N_("In se&lection"), &edit->only_in_selection),
/* 6 */
QUICK_CHECKBOX (33, SEARCH_DLG_WIDTH, 6, SEARCH_DLG_HEIGHT, N_("&Backwards"), &edit->replace_backwards),
/* 7 */
QUICK_CHECKBOX (33, SEARCH_DLG_WIDTH, 5, SEARCH_DLG_HEIGHT, N_("case &Sensitive"), &edit->replace_case),
/* 8 */
QUICK_RADIO ( 3, SEARCH_DLG_WIDTH, 5, SEARCH_DLG_HEIGHT,
g_strv_length (list_of_types), (const char **) list_of_types, &edit->search_type),
/* 9 */
QUICK_INPUT (3, SEARCH_DLG_WIDTH, 3, SEARCH_DLG_HEIGHT,
*search_text, SEARCH_DLG_WIDTH - 6, 0, MC_HISTORY_SHARED_SEARCH, search_text),
/* 10 */
QUICK_LABEL (2, SEARCH_DLG_WIDTH, 2, SEARCH_DLG_HEIGHT, N_(" Enter search string:")),
QUICK_END
};
#ifdef HAVE_CHARSET
int last_checkbox = 7;
#else
int last_checkbox = 6;
#endif
QuickDialog Quick_input =
{
SEARCH_DLG_WIDTH, SEARCH_DLG_HEIGHT, -1, -1, N_("Search"),
"[Input Line Keys]", quick_widgets, TRUE
};
#ifdef ENABLE_NLS
/* header title */
Quick_input.title = _(Quick_input.title);
/* buttons */
for (i = 0; i < 3; i++)
quick_widgets[i].u.button.text = _(quick_widgets[i].u.button.text);
/* checkboxes */
for (i = 3; i <= last_checkbox; i++)
quick_widgets[i].u.checkbox.text = _(quick_widgets[i].u.checkbox.text);
/* label */
quick_widgets[10].u.label.text = _(quick_widgets[10].u.label.text);
#endif
/* calculate widget coordinates */
{
int len = 0;
int dlg_width;
gchar **radio = list_of_types;
int b0_len, b1_len, b2_len;
const int button_gap = 2;
/* length of radiobuttons */
while (*radio != NULL) {
len = max (len, str_term_width1 (*radio));
radio++;
}
/* length of checkboxes */
for (i = 3; i <= last_checkbox; i++)
len = max (len, str_term_width1 (quick_widgets[i].u.checkbox.text) + 4);
/* preliminary dialog width */
dlg_width = max (len * 2, str_term_width1 (Quick_input.title)) + 4;
/* length of buttons */
b0_len = str_term_width1 (quick_widgets[0].u.button.text) + 3;
b1_len = str_term_width1 (quick_widgets[1].u.button.text) + 3;
b2_len = str_term_width1 (quick_widgets[2].u.button.text) + 5; /* default button */
len = b0_len + b1_len + b2_len + 6 + button_gap * 2;
/* dialog width */
Quick_input.xlen = max (SEARCH_DLG_WIDTH, max (dlg_width, len));
/* correct widget coordinates */
for (i = 0; i < sizeof (quick_widgets)/sizeof (quick_widgets[0]); i++)
quick_widgets[i].x_divisions = Quick_input.xlen;
/* checkbox positions */
for (i = 3; i <= last_checkbox; i++)
quick_widgets[i].relative_x = Quick_input.xlen/2 + 2;
/* input length */
quick_widgets[last_checkbox + 2].u.input.len = Quick_input.xlen - 6;
/* button positions */
quick_widgets[1].relative_x = Quick_input.xlen/2 - b1_len/2;
quick_widgets[2].relative_x = quick_widgets[1].relative_x - button_gap - b2_len;
quick_widgets[0].relative_x = quick_widgets[1].relative_x + button_gap + b1_len;
}
dialog_result = quick_dialog (&Quick_input);
if (dialog_result == B_CANCEL)
*search_text = NULL;
else if (dialog_result == B_USER)
search_create_bookmark = 1;
}
}
/* --------------------------------------------------------------------------------------------- */
/* gets a raw key from the keyboard. Passing cancel = 1 draws
a cancel button thus allowing c-c etc. Alternatively, cancel = 0
will return the next key pressed. ctrl-a (=B_CANCEL), ctrl-g, ctrl-c,
and Esc are cannot returned */
int
editcmd_dialog_raw_key_query (const char *heading, const char *query, int cancel)
{
int w = str_term_width1 (query) + 7;
struct Dlg_head *raw_dlg =
create_dlg (0, 0, 7, w, dialog_colors, editcmd_dialog_raw_key_query_cb,
NULL, heading,
DLG_CENTER | DLG_TRYUP | DLG_WANT_TAB);
add_widget (raw_dlg,
input_new (3 - cancel, w - 5, INPUT_COLOR, 2, "", 0, INPUT_COMPLETE_DEFAULT));
add_widget (raw_dlg, label_new (3 - cancel, 2, query));
if (cancel)
add_widget (raw_dlg, button_new (4, w / 2 - 5, B_CANCEL, NORMAL_BUTTON, _("Cancel"), 0));
w = run_dlg (raw_dlg);
destroy_dlg (raw_dlg);
if (cancel) {
if (w == XCTRL ('g') || w == XCTRL ('c') || w == ESC_CHAR || w == B_CANCEL)
return 0;
}
return w;
}
/* --------------------------------------------------------------------------------------------- */
/* let the user select its preferred completion */
void
editcmd_dialog_completion_show (WEdit * edit, int max_len, int word_len,
struct selection *compl, int num_compl)
{
int start_x, start_y, offset, i;
char *curr = NULL;
Dlg_head *compl_dlg;
WListbox *compl_list;
int compl_dlg_h; /* completion dialog height */
int compl_dlg_w; /* completion dialog width */
/* calculate the dialog metrics */
compl_dlg_h = num_compl + 2;
compl_dlg_w = max_len + 4;
start_x = edit->curs_col + edit->start_col - (compl_dlg_w / 2) +
EDIT_TEXT_HORIZONTAL_OFFSET + option_line_state_width;
start_y = edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + 1;
if (start_x < 0)
start_x = 0;
if (compl_dlg_w > COLS)
compl_dlg_w = COLS;
if (compl_dlg_h > LINES - 2)
compl_dlg_h = LINES - 2;
offset = start_x + compl_dlg_w - COLS;
if (offset > 0)
start_x -= offset;
offset = start_y + compl_dlg_h - LINES;
if (offset > 0)
start_y -= (offset + 1);
/* create the dialog */
compl_dlg =
create_dlg (start_y, start_x, compl_dlg_h, compl_dlg_w,
dialog_colors, NULL, "[Completion]", NULL, DLG_COMPACT);
/* create the listbox */
compl_list = listbox_new (1, 1, compl_dlg_h - 2, compl_dlg_w - 2, NULL);
/* add the dialog */
add_widget (compl_dlg, compl_list);
/* fill the listbox with the completions */
for (i = num_compl - 1; i >= 0; i--) /* reverse order */
listbox_add_item (compl_list, LISTBOX_APPEND_AT_END, 0, (char *) compl[i].text, NULL);
/* pop up the dialog and apply the choosen completion */
if (run_dlg (compl_dlg) == B_ENTER) {
listbox_get_current (compl_list, &curr, NULL);
if (curr)
for (curr += word_len; *curr; curr++)
edit_insert (edit, *curr);
}
/* destroy dialog before return */
destroy_dlg (compl_dlg);
}
/* --------------------------------------------------------------------------------------------- */
/* let the user select where function definition */
void
editcmd_dialog_select_definition_show (WEdit * edit, char *match_expr, int max_len, int word_len,
etags_hash_t * def_hash, int num_lines)
{
int start_x, start_y, offset, i;
char *curr = NULL;
etags_hash_t *curr_def = NULL;
Dlg_head *def_dlg;
WListbox *def_list;
int def_dlg_h; /* dialog height */
int def_dlg_w; /* dialog width */
char *label_def = NULL;
(void) word_len;
/* calculate the dialog metrics */
def_dlg_h = num_lines + 2;
def_dlg_w = max_len + 4;
start_x = edit->curs_col + edit->start_col - (def_dlg_w / 2) +
EDIT_TEXT_HORIZONTAL_OFFSET + option_line_state_width;
start_y = edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + 1;
if (start_x < 0)
start_x = 0;
if (def_dlg_w > COLS)
def_dlg_w = COLS;
if (def_dlg_h > LINES - 2)
def_dlg_h = LINES - 2;
offset = start_x + def_dlg_w - COLS;
if (offset > 0)
start_x -= offset;
offset = start_y + def_dlg_h - LINES;
if (offset > 0)
start_y -= (offset + 1);
/* create the dialog */
def_dlg = create_dlg (start_y, start_x, def_dlg_h, def_dlg_w,
dialog_colors, NULL, "[Definitions]", match_expr, DLG_COMPACT);
/* create the listbox */
def_list = listbox_new (1, 1, def_dlg_h - 2, def_dlg_w - 2, NULL);
/* add the dialog */
add_widget (def_dlg, def_list);
/* fill the listbox with the completions */
for (i = 0; i < num_lines; i++) {
label_def =
g_strdup_printf ("%s -> %s:%ld", def_hash[i].short_define, def_hash[i].filename,
def_hash[i].line);
listbox_add_item (def_list, LISTBOX_APPEND_AT_END, 0, label_def, &def_hash[i]);
g_free (label_def);
}
/* pop up the dialog */
run_dlg (def_dlg);
/* apply the choosen completion */
if (def_dlg->ret_value == B_ENTER) {
char *tmp_curr_def = (char *) curr_def;
int do_moveto = 0;
listbox_get_current (def_list, &curr, &tmp_curr_def);
curr_def = (etags_hash_t *) tmp_curr_def;
if (edit->modified) {
if (!edit_query_dialog2
(_("Warning"),
_(" Current text was modified without a file save. \n"
" Continue discards these changes. "), _("C&ontinue"), _("&Cancel"))) {
edit->force |= REDRAW_COMPLETELY;
do_moveto = 1;
}
} else {
do_moveto = 1;
}
if (curr && do_moveto) {
if (edit_stack_iterator + 1 < MAX_HISTORY_MOVETO) {
g_free (edit_history_moveto[edit_stack_iterator].filename);
if (edit->dir) {
edit_history_moveto[edit_stack_iterator].filename =
g_strdup_printf ("%s/%s", edit->dir, edit->filename);
} else {
edit_history_moveto[edit_stack_iterator].filename = g_strdup (edit->filename);
}
edit_history_moveto[edit_stack_iterator].line = edit->start_line +
edit->curs_row + 1;
edit_stack_iterator++;
g_free (edit_history_moveto[edit_stack_iterator].filename);
edit_history_moveto[edit_stack_iterator].filename =
g_strdup ((char *) curr_def->fullpath);
edit_history_moveto[edit_stack_iterator].line = curr_def->line;
edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename,
edit_history_moveto[edit_stack_iterator].line);
}
}
}
/* clear definition hash */
for (i = 0; i < MAX_DEFINITIONS; i++) {
g_free (def_hash[i].filename);
}
/* destroy dialog before return */
destroy_dlg (def_dlg);
}
/* --------------------------------------------------------------------------------------------- */
int
editcmd_dialog_replace_prompt_show (WEdit * edit, char *from_text, char *to_text, int xpos, int ypos)
{
/* dialog sizes */
int dlg_height = 9;
int dlg_width = 8;
int retval;
int i;
int btn_pos;
char *repl_from, *repl_to;
char tmp [BUF_MEDIUM];
QuickWidget quick_widgets[] =
{
/* 0 */ QUICK_BUTTON (44, dlg_width, 6, dlg_height, N_("&Cancel"), B_CANCEL, NULL),
/* 1 */ QUICK_BUTTON (29, dlg_width, 6, dlg_height, N_("&Skip"), B_SKIP_REPLACE, NULL),
/* 2 */ QUICK_BUTTON (21, dlg_width, 6, dlg_height, N_("A&ll"), B_REPLACE_ALL, NULL),
/* 3 */ QUICK_BUTTON ( 4, dlg_width, 6, dlg_height, N_("&Replace"), B_ENTER, NULL),
/* 4 */ QUICK_LABEL ( 3, dlg_width, 2, dlg_height, NULL),
/* 5 */ QUICK_LABEL ( 2, dlg_width, 3, dlg_height, N_(" Replace with: ")),
/* 6 */ QUICK_LABEL ( 3, dlg_width, 4, dlg_height, NULL),
QUICK_END
};
#ifdef ENABLE_NLS
for (i = 0; i < 4; i++)
quick_widgets[i].u.button.text = _(quick_widgets[i].u.button.text);
#endif
/* calculate button positions */
btn_pos = 4;
for (i = 3; i > -1; i--) {
quick_widgets[i].relative_x = btn_pos;
btn_pos += str_term_width1 (quick_widgets[i].u.button.text) + 5;
if (i == 3) /* default button */
btn_pos += 2;
}
dlg_width = btn_pos + 2;
/* correct widget coordinates */
for (i = 0; i < 7; i++)
quick_widgets[i].x_divisions = dlg_width;
g_snprintf (tmp, sizeof (tmp), " '%s'", from_text);
repl_from = g_strdup (str_fit_to_term (tmp, dlg_width - 7, J_LEFT));
g_snprintf (tmp, sizeof (tmp), " '%s'", to_text);
repl_to = g_strdup (str_fit_to_term (tmp, dlg_width - 7, J_LEFT));
quick_widgets[4].u.label.text = repl_from;
quick_widgets[6].u.label.text = repl_to;
if (xpos == -1)
xpos = (edit->num_widget_columns - dlg_width) / 2;
if (ypos == -1)
ypos = edit->num_widget_lines * 2 / 3;
{
QuickDialog Quick_input =
{
dlg_width, dlg_height, 0, 0, N_ (" Confirm replace "),
"[Input Line Keys]", quick_widgets, FALSE
};
/* Sometimes menu can hide replaced text. I don't like it */
if ((edit->curs_row >= ypos - 1) && (edit->curs_row <= ypos + dlg_height - 1))
ypos -= dlg_height;
Quick_input.ypos = ypos;
Quick_input.xpos = xpos;
retval = quick_dialog (&Quick_input);
g_free (repl_from);
g_free (repl_to);
return retval;
}
}
/* --------------------------------------------------------------------------------------------- */