1
/* Copyright 2009-2010 Yorba Foundation
3
* This software is licensed under the GNU Lesser General Public License
4
* (version 2.1 or later). See the COPYING file in this distribution.
10
class AutocompleteDialog : Object {
11
weak Gedit.Window parent;
18
public AutocompleteDialog(Gedit.Window parent_win) {
21
inserting_text = false;
22
list = new ListViewString(Gtk.TreeViewColumnSizing.AUTOSIZE, 100);
23
list.row_activated += select_item;
25
window = new Gtk.Window(Gtk.WindowType.POPUP);
26
window.add(list.scrolled_window);
27
window.set_destroy_with_parent(true);
28
window.set_default_size(200, 1);
29
window.set_resizable(true);
31
window.set_border_width(1);
36
Signal.connect(window, "expose-event", (Callback) draw_callback, this);
39
static bool draw_callback(Gtk.Window window, Gdk.EventExpose event, AutocompleteDialog dialog) {
40
Gtk.paint_flat_box(dialog.window.style, dialog.window.window,
41
Gtk.StateType.NORMAL, Gtk.ShadowType.OUT,
42
null, dialog.window, "tooltip",
43
dialog.window.allocation.x, dialog.window.allocation.y,
44
dialog.window.allocation.width, dialog.window.allocation.height);
46
dialog.list.scrolled_window.expose_event(event);
51
unowned string? get_completion_target(Gtk.TextBuffer buffer) {
52
Gtk.TextIter start = get_insert_iter(buffer);
53
Gtk.TextIter end = start;
56
start.backward_char();
57
unichar c = start.get_char();
58
if (!c.isalnum() && c != '.' && c != '_')
61
// Only include characters in the ID name
64
if (start.get_offset() == end.get_offset())
67
return start.get_slice(end);
70
string strip_completed_classnames(string list_name, string completion_target) {
71
string[] classnames = completion_target.split(".");
72
int names = classnames.length;
73
// If the last classname is not explicitly part of the class qualification, then it
74
// should not be removed from the completion suggestion's name
75
if (!completion_target.has_suffix("."))
78
for (int i = 0; i < names; ++i) {
79
weak string name = classnames[i];
81
// If the name doesn't contain the current classname, it may be a namespace name that
82
// isn't part of the list_name string - we shouldn't stop the comparison early
83
if (list_name.contains(name)) {
84
// Add one to the offset of a string to account for the "."
85
long offset = name.length;
88
list_name = list_name.offset(offset);
95
string parse_single_symbol(Symbol symbol, string? completion_target, bool constructor) {
96
string list_name = "";
99
// Get the fully-qualified constructor name
100
Constructor c = symbol as Constructor;
103
list_name = c.parent.to_string();
106
list_name += "." + c.name;
109
// If the user hasn't typed anything or if either the completion string or this
110
// constructor is not qualified, keep the original name
111
if (completion_target != null && completion_target.contains(".")
112
&& list_name.contains("."))
113
list_name = strip_completed_classnames(list_name, completion_target);
116
list_name = symbol.name;
117
if (symbol is Method && !(symbol is VSignal) && !(symbol is Delegate))
118
list_name = symbol.name + "()";
124
string[]? parse_symbol_names(HashSet<Symbol>? symbols) {
128
string[] list = new string[symbols.size];
130
// If the first element is a constructor, all elements will be constructors
131
Iterator<Symbol> iter = symbols.iterator();
133
bool constructor = iter.get() is Constructor;
135
// match the extent of what the user has already typed with named constructors
136
string? completion_target = null;
138
completion_target = get_completion_target(parent.get_active_document());
142
foreach (Symbol symbol in symbols) {
143
list[i] = parse_single_symbol(symbol, completion_target, constructor);
147
qsort(list, symbols.size, sizeof(string), (GLib.CompareFunc) compare_string);
151
public void show(SymbolSet symbol_set) {
157
partial_name = symbol_set.get_name();
159
weak HashSet<Symbol>? symbols = symbol_set.get_symbols();
160
string[]? symbol_strings = parse_symbol_names(symbols);
162
if (symbol_strings != null) {
163
foreach (string s in symbol_strings) {
171
// TODO: this must be updated to account for font size changes when adding ticket #560
172
int size = list.size();
174
list.set_vscrollbar_policy(Gtk.PolicyType.AUTOMATIC);
175
window.resize(200, 140);
177
list.set_vscrollbar_policy(Gtk.PolicyType.NEVER);
178
window.resize(200, size * 23);
181
Gedit.Document document = parent.get_active_document();
182
Gtk.TextMark insert_mark = document.get_insert();
183
Gtk.TextIter insert_iter;
184
document.get_iter_at_mark(out insert_iter, insert_mark);
186
get_coords_at_buffer_offset(parent, insert_iter.get_offset(), false, true, out x, out y);
202
public bool is_visible() {
206
public void select_first_cell() {
207
list.select_first_cell();
210
public void select_last_cell() {
211
list.select_last_cell();
214
public void select_previous() {
215
list.select_previous();
218
public void select_next() {
222
public void page_up() {
226
public void page_down() {
230
public void select_item() {
231
string selection = list.get_selected_item();
232
Gedit.Document buffer = parent.get_active_document();
234
// delete the whole string to be autocompleted and replace it (the case may not match)
235
Gtk.TextIter start = get_insert_iter(buffer);
237
if (!start.backward_char())
239
unichar c = start.get_char();
240
if (!c.isalnum() && c != '_')
243
// don't include the nonalphanumeric character
244
start.forward_char();
246
Gtk.TextIter end = start;
248
unichar c = end.get_char();
253
if (!c.isalnum() && c != '_' && c != '.')
255
if (!end.forward_char())
259
// Text insertion/deletion signals have been linked to updating the autocomplete dialog -
260
// we don't want to do that if we're already inserting text.
261
inserting_text = true;
262
buffer.delete(start, end);
264
long offset = selection.has_suffix(")") ? 1 : 0;
265
buffer.insert_at_cursor(selection, (int) (selection.length - offset));
266
inserting_text = false;