4
* Copyright 2009 Bert Vermeulen <bert@biot.com>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25
#include <gdk/gdkkeysyms.h>
29
#include "sciwrappers.h"
36
#include "plugindata.h"
37
#include "geanyfunctions.h"
43
#define DEFAULT_TOKENS { "TODO", "FIXME", NULL };
52
static GString *linebuf = NULL;
53
static char *tokens[] = DEFAULT_TOKENS;
54
static GHashTable *globaltasks = NULL;
55
static GtkListStore *taskstore = NULL;
56
static GtkWidget *notebook_page = NULL;
57
static gboolean tasks_enabled = FALSE;
60
static gboolean tasks_button_cb(GtkWidget *widget, GdkEventButton *event, gpointer data);
61
static gboolean tasks_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data);
62
static void free_editor_tasks(gpointer key, gpointer value, gpointer data);
63
static void scan_all_documents(void);
64
static void scan_document_for_tasks(GeanyDocument *doc);
65
static void create_tasks_tab(void);
66
static int scan_line_for_tokens(ScintillaObject *sci, unsigned int line);
67
static int scan_buf_for_tokens(char *buf);
68
static GeanyTask *create_task(unsigned int line, char *description);
69
static int find_line(GeanyTask *task, unsigned int *line);
70
static void found_token(GeanyEditor *editor, unsigned int line, char *d);
71
static void no_token(GeanyEditor *editor, unsigned int line);
72
static void lines_moved(GeanyEditor *editor, unsigned int line, int change);
73
static int keysort(GeanyTask *a, GeanyTask *b);
74
static void render_taskstore(GeanyEditor *editor);
77
static void tasks_init(void)
79
globaltasks = g_hash_table_new(NULL, NULL);
80
linebuf = g_string_sized_new(256);
87
static void tasks_cleanup(void)
92
g_string_free(linebuf, TRUE);
94
g_hash_table_foreach(globaltasks, free_editor_tasks, NULL);
95
g_hash_table_destroy(globaltasks);
97
notebook = ui_lookup_widget(geany->main_widgets->window, "notebook_info");
98
page = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), notebook_page);
99
gtk_notebook_remove_page(GTK_NOTEBOOK(notebook), page);
101
tasks_enabled = FALSE;
104
void tasks_set_enable(gboolean enable)
106
if (tasks_enabled != enable)
115
void tasks_on_document_close(GObject *object, GeanyDocument *doc, gpointer data)
118
if(tasks_enabled && doc->is_valid)
119
free_editor_tasks(doc->editor, NULL, NULL);
124
void tasks_on_document_open(GObject *object, GeanyDocument *doc, gpointer data)
127
if(tasks_enabled && doc->is_valid)
128
scan_document_for_tasks(doc);
133
void tasks_on_document_activate(GObject *object, GeanyDocument *doc, gpointer data)
136
if(tasks_enabled && doc->is_valid)
137
render_taskstore(doc->editor);
142
gboolean tasks_on_editor_notify(GObject *object, GeanyEditor *editor,
143
SCNotification *nt, gpointer data)
145
static int mod_line = -1;
146
int pos, line, offset;
151
switch (nt->nmhdr.code)
154
line = sci_get_line_from_position(editor->sci, nt->position);
155
if(nt->linesAdded != 0)
156
/* check if existing tasks had their line numbers changed */
157
lines_moved(editor, line, nt->linesAdded);
159
/* same-line change: we'll check it later */
163
pos = sci_get_current_position(editor->sci);
164
line = sci_get_line_from_position(editor->sci, pos);
165
if(mod_line != -1 && line != mod_line)
167
/* cursor left a line that was changed, scan it for tokens */
168
offset = scan_line_for_tokens(editor->sci, mod_line);
170
found_token(editor, mod_line, linebuf->str + offset);
172
no_token(editor, mod_line);
173
render_taskstore(editor);
183
static gboolean tasks_button_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
187
GtkTreeSelection *selection;
190
gboolean ret = FALSE;
193
if (event->button == 1)
197
tv = GTK_TREE_VIEW(ui_lookup_widget(geany->main_widgets->window, "treeview_tasks"));
198
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
199
if (gtk_tree_selection_get_selected(selection, &model, &iter))
201
gtk_tree_model_get(model, &iter, 0, &line, -1);
202
doc = document_get_current();
203
ret = navqueue_goto_line(NULL, doc, line + 1);
211
static gboolean tasks_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
214
GdkEventButton button_event;
216
if(event->keyval == GDK_Return ||
217
event->keyval == GDK_ISO_Enter ||
218
event->keyval == GDK_KP_Enter ||
219
event->keyval == GDK_space)
221
button_event.button = 1;
222
button_event.time = event->time;
223
tv = GTK_TREE_VIEW(ui_lookup_widget(geany->main_widgets->window, "treeview_tasks"));
224
tasks_button_cb(NULL, &button_event, tv);
233
static void free_editor_tasks(gpointer key, gpointer value, gpointer data)
235
GList *tasklist, *entry;
238
tasklist = (value) ? value : g_hash_table_lookup(globaltasks, key);
241
for(entry = g_list_first(tasklist); entry; entry = g_list_next(entry))
243
task = (GeanyTask *) entry->data;
244
g_string_free(task->description, TRUE);
247
g_list_free(tasklist);
249
g_hash_table_remove(globaltasks, key);
254
static void scan_all_documents(void)
258
for(i = 0; i < geany->documents_array->len; i++)
260
if(document_index(i)->is_valid)
262
scan_document_for_tasks(document_index(i));
268
/* go through every line of a document and scan it for tasks tokens. add the
269
* task to the tasklist for that document if found. */
270
static void scan_document_for_tasks(GeanyDocument *doc)
272
unsigned int lines, line, offset;
274
lines = sci_get_line_count(doc->editor->sci);
275
for(line = 0; line < lines; line++)
277
offset = scan_line_for_tokens(doc->editor->sci, line);
279
found_token(doc->editor, line, linebuf->str + offset);
281
render_taskstore(doc->editor);
286
static void create_tasks_tab(void)
288
GtkWidget *tv, *notebook;
289
GtkCellRenderer *renderer;
290
GtkTreeViewColumn *column;
291
GtkTreeSelection *selection;
294
taskstore = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
295
tv = gtk_tree_view_new_with_model(GTK_TREE_MODEL(taskstore));
296
g_object_set_data(G_OBJECT(geany->main_widgets->window), "treeview_tasks", tv);
297
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv), FALSE);
298
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
299
gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
300
g_signal_connect(tv, "button-release-event", G_CALLBACK(tasks_button_cb), (gpointer) tv);
301
g_signal_connect(tv, "key-press-event", G_CALLBACK(tasks_key_cb), (gpointer) tv);
303
renderer = gtk_cell_renderer_text_new();
304
column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 1, NULL);
305
gtk_tree_view_append_column(GTK_TREE_VIEW(tv), column);
307
notebook = ui_lookup_widget(geany->main_widgets->window, "notebook_info");
308
page = gtk_notebook_insert_page(GTK_NOTEBOOK(notebook), tv, gtk_label_new(_("Tasks")), -1);
309
gtk_widget_show_all(tv);
314
/* copy the line into linebuf and scan it for tokens. returns 0 if no tokens
315
* were found, or the offset to the start of the task in linebuf otherwise. */
316
static int scan_line_for_tokens(ScintillaObject *sci, unsigned int line)
318
unsigned int len, len_done, offset;
321
len = sci_get_line_length(sci, line);
324
if(len+1 > linebuf->allocated_len)
326
/* why doesn't GString have this functionality? */
327
linebuf->str = g_realloc(linebuf->str, len+1);
328
if(linebuf->str == NULL)
330
linebuf->allocated_len = len+1;
332
len_done = scintilla_send_message(sci, SCI_GETLINE, line, (sptr_t) linebuf->str);
333
linebuf->str[len] = 0;
335
offset = scan_buf_for_tokens(linebuf->str);
342
static int scan_buf_for_tokens(char *buf)
344
unsigned int t, offset, len, i;
348
for(t = 0; tokens[t]; t++)
350
entry = strstr(buf, tokens[t]);
353
entry += strlen(tokens[t]);
354
while(*entry == ' ' || *entry == ':')
356
for(i = 0; entry[i]; i++)
358
/* strip off line endings */
359
if(entry[i] == '\t' || entry[i] == '\r' || entry[i] == '\n')
365
/* strip off */ /* no really, I mean */
367
if(len > 1 && entry[len-2] == '*' && entry[len-1] == '/')
369
offset = entry - buf;
377
static GeanyTask *create_task(unsigned int line, char *description)
381
task = malloc(sizeof(GeanyTask));
382
g_return_val_if_fail(task != NULL, NULL);
385
task->description = g_string_new(description);
391
static int find_line(GeanyTask *task, unsigned int *line)
394
if(task->line == *line)
401
static void found_token(GeanyEditor *editor, unsigned int line, char *description)
404
GList *tasklist, *entry;
406
tasklist = g_hash_table_lookup(globaltasks, editor);
409
entry = g_list_find_custom(tasklist, (gconstpointer) &line, (gconstpointer) find_line);
412
task = (GeanyTask *) entry->data;
413
if(strcmp(description, task->description->str))
414
g_string_assign(task->description, description);
418
task = create_task(line, description);
419
tasklist = g_list_append(tasklist, task);
420
g_hash_table_replace(globaltasks, editor, tasklist);
425
/* this editor doesn't have a tasklist yet */
426
task = create_task(line, description);
427
tasklist = g_list_append(NULL, task);
428
g_hash_table_insert(globaltasks, editor, tasklist);
434
/* no token was found on this line, make sure there's nothing in the tasklist either */
435
static void no_token(GeanyEditor *editor, unsigned int line)
437
GList *tasklist, *entry;
439
tasklist = g_hash_table_lookup(globaltasks, editor);
442
entry = g_list_find_custom(tasklist, (gconstpointer) &line, (gconstpointer) find_line);
445
tasklist = g_list_remove(tasklist, entry);
446
g_hash_table_replace(globaltasks, editor, tasklist);
453
static void lines_moved(GeanyEditor *editor, unsigned int line, int change)
456
GList *tasklist, *entry, *to_delete;
459
tasklist = g_hash_table_lookup(globaltasks, editor);
460
for(entry = g_list_first(tasklist); entry; entry = g_list_next(entry))
462
task = (GeanyTask *) entry->data;
463
if(task->line >= line)
465
if(change < 0 && task->line < line - change)
466
/* the line with this task on it was deleted, so mark the task for deletion */
467
to_delete = g_list_append(to_delete, entry->data);
469
/* shift the line number of this task up or down along with the change */
470
task->line += change;
474
for(entry = g_list_first(to_delete); entry; entry = g_list_next(entry))
476
task = (GeanyTask *) entry->data;
477
tasklist = g_list_remove(tasklist, entry->data);
478
g_string_free(task->description, TRUE);
481
g_list_free(to_delete);
483
g_hash_table_replace(globaltasks, editor, tasklist);
484
render_taskstore(editor);
489
static int keysort(GeanyTask *a, GeanyTask *b)
492
if(a->line < b->line)
494
else if(a->line > b->line)
501
static void render_taskstore(GeanyEditor *editor)
505
GList *tasklist, *entry;
507
gtk_list_store_clear(taskstore);
508
tasklist = g_hash_table_lookup(globaltasks, editor);
512
tasklist = g_list_sort(tasklist, (GCompareFunc) keysort);
513
g_hash_table_replace(globaltasks, editor, tasklist);
515
for(entry = g_list_first(tasklist); entry; entry = g_list_next(entry))
517
task = (GeanyTask *) entry->data;
518
gtk_list_store_append(taskstore, &iter);
519
gtk_list_store_set(taskstore, &iter, 0, task->line, 1, task->description->str, -1);