~ubuntu-branches/debian/squeeze/geany-plugins/squeeze

« back to all changes in this revision

Viewing changes to addons/src/tasks.c

  • Committer: Bazaar Package Importer
  • Author(s): Chow Loong Jin
  • Date: 2009-07-10 22:56:41 UTC
  • Revision ID: james.westby@ubuntu.com-20090710225641-xc1126t7pq0jmpos
Tags: upstream-0.17.1
ImportĀ upstreamĀ versionĀ 0.17.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *      tasks - tasks.c
 
3
 *
 
4
 *      Copyright 2009 Bert Vermeulen <bert@biot.com>
 
5
 *
 
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.
 
10
 *
 
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.
 
15
 *
 
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,
 
19
 *      MA 02110-1301, USA.
 
20
 */
 
21
 
 
22
#include <stdlib.h>
 
23
#include <string.h>
 
24
 
 
25
#include <gdk/gdkkeysyms.h>
 
26
 
 
27
#include "geany.h"
 
28
#include "support.h"
 
29
#include "sciwrappers.h"
 
30
 
 
31
#include "prefs.h"
 
32
#include "ui_utils.h"
 
33
#include "utils.h"
 
34
#include "document.h"
 
35
 
 
36
#include "plugindata.h"
 
37
#include "geanyfunctions.h"
 
38
 
 
39
#include "addons.h"
 
40
#include "tasks.h"
 
41
 
 
42
 
 
43
#define DEFAULT_TOKENS { "TODO", "FIXME", NULL };
 
44
 
 
45
 
 
46
typedef struct {
 
47
        unsigned int line;
 
48
        GString *description;
 
49
} GeanyTask;
 
50
 
 
51
 
 
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;
 
58
 
 
59
 
 
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);
 
75
 
 
76
 
 
77
static void tasks_init(void)
 
78
{
 
79
        globaltasks = g_hash_table_new(NULL, NULL);
 
80
        linebuf = g_string_sized_new(256);
 
81
        create_tasks_tab();
 
82
        scan_all_documents();
 
83
 
 
84
        tasks_enabled = TRUE;
 
85
}
 
86
 
 
87
static void tasks_cleanup(void)
 
88
{
 
89
        GtkWidget *notebook;
 
90
        int page;
 
91
 
 
92
        g_string_free(linebuf, TRUE);
 
93
 
 
94
        g_hash_table_foreach(globaltasks, free_editor_tasks, NULL);
 
95
        g_hash_table_destroy(globaltasks);
 
96
 
 
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);
 
100
 
 
101
        tasks_enabled = FALSE;
 
102
}
 
103
 
 
104
void tasks_set_enable(gboolean enable)
 
105
{
 
106
        if (tasks_enabled != enable)
 
107
        {
 
108
                if (enable)
 
109
                        tasks_init();
 
110
                else
 
111
                        tasks_cleanup();
 
112
        }
 
113
}
 
114
 
 
115
void tasks_on_document_close(GObject *object, GeanyDocument *doc, gpointer data)
 
116
{
 
117
 
 
118
        if(tasks_enabled && doc->is_valid)
 
119
                free_editor_tasks(doc->editor, NULL, NULL);
 
120
 
 
121
}
 
122
 
 
123
 
 
124
void tasks_on_document_open(GObject *object, GeanyDocument *doc, gpointer data)
 
125
{
 
126
 
 
127
        if(tasks_enabled && doc->is_valid)
 
128
                scan_document_for_tasks(doc);
 
129
 
 
130
}
 
131
 
 
132
 
 
133
void tasks_on_document_activate(GObject *object, GeanyDocument *doc, gpointer data)
 
134
{
 
135
 
 
136
        if(tasks_enabled && doc->is_valid)
 
137
                render_taskstore(doc->editor);
 
138
 
 
139
}
 
140
 
 
141
 
 
142
gboolean tasks_on_editor_notify(GObject *object, GeanyEditor *editor,
 
143
                                                                 SCNotification *nt, gpointer data)
 
144
{
 
145
        static int mod_line = -1;
 
146
        int pos, line, offset;
 
147
 
 
148
        if (! tasks_enabled)
 
149
                return FALSE;
 
150
 
 
151
        switch (nt->nmhdr.code)
 
152
        {
 
153
                case SCN_MODIFIED:
 
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);
 
158
                        else
 
159
                                /* same-line change: we'll check it later */
 
160
                                mod_line = line;
 
161
                        break;
 
162
                case SCN_UPDATEUI:
 
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)
 
166
                        {
 
167
                                /* cursor left a line that was changed, scan it for tokens */
 
168
                                offset = scan_line_for_tokens(editor->sci, mod_line);
 
169
                                if(offset)
 
170
                                        found_token(editor, mod_line, linebuf->str + offset);
 
171
                                else
 
172
                                        no_token(editor, mod_line);
 
173
                                render_taskstore(editor);
 
174
                                mod_line = -1;
 
175
                        }
 
176
                        break;
 
177
        }
 
178
 
 
179
        return FALSE;
 
180
}
 
181
 
 
182
 
 
183
static gboolean tasks_button_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
 
184
{
 
185
        GeanyDocument *doc;
 
186
        GtkTreeView *tv;
 
187
        GtkTreeSelection *selection;
 
188
        GtkTreeIter iter;
 
189
        GtkTreeModel *model;
 
190
        gboolean ret = FALSE;
 
191
        unsigned int line;
 
192
 
 
193
        if (event->button == 1)
 
194
        {
 
195
                ret = TRUE;
 
196
 
 
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))
 
200
                {
 
201
                        gtk_tree_model_get(model, &iter, 0, &line, -1);
 
202
                        doc = document_get_current();
 
203
                        ret = navqueue_goto_line(NULL, doc, line + 1);
 
204
                }
 
205
        }
 
206
 
 
207
        return ret;
 
208
}
 
209
 
 
210
 
 
211
static gboolean tasks_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
 
212
{
 
213
        GtkTreeView *tv;
 
214
        GdkEventButton button_event;
 
215
 
 
216
        if(event->keyval == GDK_Return ||
 
217
                event->keyval == GDK_ISO_Enter ||
 
218
                event->keyval == GDK_KP_Enter ||
 
219
                event->keyval == GDK_space)
 
220
        {
 
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);
 
225
 
 
226
                return TRUE;
 
227
        }
 
228
 
 
229
        return FALSE;
 
230
}
 
231
 
 
232
 
 
233
static void free_editor_tasks(gpointer key, gpointer value, gpointer data)
 
234
{
 
235
        GList *tasklist, *entry;
 
236
        GeanyTask *task;
 
237
 
 
238
        tasklist = (value) ? value : g_hash_table_lookup(globaltasks, key);
 
239
        if(tasklist)
 
240
        {
 
241
                for(entry = g_list_first(tasklist); entry; entry = g_list_next(entry))
 
242
                {
 
243
                        task = (GeanyTask *) entry->data;
 
244
                        g_string_free(task->description, TRUE);
 
245
                        g_free(task);
 
246
                }
 
247
                g_list_free(tasklist);
 
248
        }
 
249
        g_hash_table_remove(globaltasks, key);
 
250
 
 
251
}
 
252
 
 
253
 
 
254
static void scan_all_documents(void)
 
255
{
 
256
        unsigned int i;
 
257
 
 
258
        for(i = 0; i < geany->documents_array->len; i++)
 
259
        {
 
260
                if(document_index(i)->is_valid)
 
261
                {
 
262
                        scan_document_for_tasks(document_index(i));
 
263
                }
 
264
        }
 
265
 
 
266
}
 
267
 
 
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)
 
271
{
 
272
        unsigned int lines, line, offset;
 
273
 
 
274
        lines = sci_get_line_count(doc->editor->sci);
 
275
        for(line = 0; line < lines; line++)
 
276
        {
 
277
                offset = scan_line_for_tokens(doc->editor->sci, line);
 
278
                if(offset)
 
279
                        found_token(doc->editor, line, linebuf->str + offset);
 
280
        }
 
281
        render_taskstore(doc->editor);
 
282
 
 
283
}
 
284
 
 
285
 
 
286
static void create_tasks_tab(void)
 
287
{
 
288
        GtkWidget *tv, *notebook;
 
289
        GtkCellRenderer *renderer;
 
290
        GtkTreeViewColumn *column;
 
291
        GtkTreeSelection *selection;
 
292
        int page;
 
293
 
 
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);
 
302
 
 
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);
 
306
 
 
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);
 
310
        notebook_page = tv;
 
311
}
 
312
 
 
313
 
 
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)
 
317
{
 
318
        unsigned int len, len_done, offset;
 
319
 
 
320
        offset = 0;
 
321
        len = sci_get_line_length(sci, line);
 
322
        if(len)
 
323
        {
 
324
                if(len+1 > linebuf->allocated_len)
 
325
                {
 
326
                        /* why doesn't GString have this functionality? */
 
327
                        linebuf->str = g_realloc(linebuf->str, len+1);
 
328
                        if(linebuf->str == NULL)
 
329
                                return 0;
 
330
                        linebuf->allocated_len = len+1;
 
331
                }
 
332
                len_done = scintilla_send_message(sci, SCI_GETLINE, line, (sptr_t) linebuf->str);
 
333
                linebuf->str[len] = 0;
 
334
                if(len_done)
 
335
                        offset = scan_buf_for_tokens(linebuf->str);
 
336
        }
 
337
 
 
338
        return offset;
 
339
}
 
340
 
 
341
 
 
342
static int scan_buf_for_tokens(char *buf)
 
343
{
 
344
        unsigned int t, offset, len, i;
 
345
        char *entry;
 
346
 
 
347
        offset = 0;
 
348
        for(t = 0; tokens[t]; t++)
 
349
        {
 
350
                entry = strstr(buf, tokens[t]);
 
351
                if(entry)
 
352
                {
 
353
                        entry += strlen(tokens[t]);
 
354
                        while(*entry == ' ' || *entry == ':')
 
355
                                entry++;
 
356
                        for(i = 0; entry[i]; i++)
 
357
                        {
 
358
                                /* strip off line endings */
 
359
                                if(entry[i] == '\t' || entry[i] == '\r' || entry[i] == '\n')
 
360
                                {
 
361
                                        entry[i] = 0;
 
362
                                        break;
 
363
                                }
 
364
                        }
 
365
                        /* strip off */ /* no really, I mean */
 
366
                        len = strlen(entry);
 
367
                        if(len > 1 && entry[len-2] == '*' && entry[len-1] == '/')
 
368
                                entry[len-2] = 0;
 
369
                        offset = entry - buf;
 
370
                }
 
371
        }
 
372
 
 
373
        return offset;
 
374
}
 
375
 
 
376
 
 
377
static GeanyTask *create_task(unsigned int line, char *description)
 
378
{
 
379
        GeanyTask *task;
 
380
 
 
381
        task = malloc(sizeof(GeanyTask));
 
382
        g_return_val_if_fail(task != NULL, NULL);
 
383
 
 
384
        task->line = line;
 
385
        task->description = g_string_new(description);
 
386
 
 
387
        return task;
 
388
}
 
389
 
 
390
 
 
391
static int find_line(GeanyTask *task, unsigned int *line)
 
392
{
 
393
 
 
394
        if(task->line == *line)
 
395
                return 0;
 
396
 
 
397
        return 1;
 
398
}
 
399
 
 
400
 
 
401
static void found_token(GeanyEditor *editor, unsigned int line, char *description)
 
402
{
 
403
        GeanyTask *task;
 
404
        GList *tasklist, *entry;
 
405
 
 
406
        tasklist = g_hash_table_lookup(globaltasks, editor);
 
407
        if(tasklist)
 
408
        {
 
409
                entry = g_list_find_custom(tasklist, (gconstpointer) &line, (gconstpointer) find_line);
 
410
                if(entry)
 
411
                {
 
412
                        task = (GeanyTask *) entry->data;
 
413
                        if(strcmp(description, task->description->str))
 
414
                                g_string_assign(task->description, description);
 
415
                }
 
416
                else
 
417
                {
 
418
                        task = create_task(line, description);
 
419
                        tasklist = g_list_append(tasklist, task);
 
420
                        g_hash_table_replace(globaltasks, editor, tasklist);
 
421
                }
 
422
        }
 
423
        else
 
424
        {
 
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);
 
429
        }
 
430
 
 
431
}
 
432
 
 
433
 
 
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)
 
436
{
 
437
        GList *tasklist, *entry;
 
438
 
 
439
        tasklist = g_hash_table_lookup(globaltasks, editor);
 
440
        if(tasklist)
 
441
        {
 
442
                entry = g_list_find_custom(tasklist, (gconstpointer) &line, (gconstpointer) find_line);
 
443
                if(entry)
 
444
                {
 
445
                        tasklist = g_list_remove(tasklist, entry);
 
446
                        g_hash_table_replace(globaltasks, editor, tasklist);
 
447
                }
 
448
        }
 
449
 
 
450
}
 
451
 
 
452
 
 
453
static void lines_moved(GeanyEditor *editor, unsigned int line, int change)
 
454
{
 
455
        GeanyTask *task;
 
456
        GList *tasklist, *entry, *to_delete;
 
457
 
 
458
        to_delete = NULL;
 
459
        tasklist = g_hash_table_lookup(globaltasks, editor);
 
460
        for(entry = g_list_first(tasklist); entry; entry = g_list_next(entry))
 
461
        {
 
462
                task = (GeanyTask *) entry->data;
 
463
                if(task->line >= line)
 
464
                {
 
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);
 
468
                        else
 
469
                                /* shift the line number of this task up or down along with the change */
 
470
                                task->line += change;
 
471
                }
 
472
        }
 
473
 
 
474
        for(entry = g_list_first(to_delete); entry; entry = g_list_next(entry))
 
475
        {
 
476
                task = (GeanyTask *) entry->data;
 
477
                tasklist = g_list_remove(tasklist, entry->data);
 
478
                g_string_free(task->description, TRUE);
 
479
                g_free(task);
 
480
        }
 
481
        g_list_free(to_delete);
 
482
 
 
483
        g_hash_table_replace(globaltasks, editor, tasklist);
 
484
        render_taskstore(editor);
 
485
 
 
486
}
 
487
 
 
488
 
 
489
static int keysort(GeanyTask *a, GeanyTask *b)
 
490
{
 
491
 
 
492
        if(a->line < b->line)
 
493
                return -1;
 
494
        else if(a->line > b->line)
 
495
                return 1;
 
496
 
 
497
        return 0;
 
498
}
 
499
 
 
500
 
 
501
static void render_taskstore(GeanyEditor *editor)
 
502
{
 
503
        GeanyTask *task;
 
504
        GtkTreeIter iter;
 
505
        GList *tasklist, *entry;
 
506
 
 
507
        gtk_list_store_clear(taskstore);
 
508
        tasklist = g_hash_table_lookup(globaltasks, editor);
 
509
        if(!tasklist)
 
510
                /* empty list */
 
511
                return;
 
512
        tasklist = g_list_sort(tasklist, (GCompareFunc) keysort);
 
513
        g_hash_table_replace(globaltasks, editor, tasklist);
 
514
 
 
515
        for(entry = g_list_first(tasklist); entry; entry = g_list_next(entry))
 
516
        {
 
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);
 
520
        }
 
521
 
 
522
}
 
523
 
 
524
 
 
525