1 /*
2 * navqueue.c - this file is part of Geany, a fast and lightweight IDE
3 *
4 * Copyright 2007 The Geany contributors
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 along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 /*
22 * Simple code navigation
23 */
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include "navqueue.h"
30
31 #include "document.h"
32 #include "geanyobject.h"
33 #include "sciwrappers.h"
34 #include "toolbar.h"
35 #include "utils.h"
36
37 #include "gtkcompat.h"
38
39
40 /* for the navigation history queue */
41 typedef struct
42 {
43 const gchar *file; /* This is the document's filename, in UTF-8 */
44 gint pos;
45 } filepos;
46
47 static GQueue *navigation_queue;
48 static guint nav_queue_pos;
49
50 static GtkAction *navigation_buttons[2];
51
52
53
navqueue_init(void)54 void navqueue_init(void)
55 {
56 navigation_queue = g_queue_new();
57 nav_queue_pos = 0;
58
59 navigation_buttons[0] = toolbar_get_action_by_name("NavBack");
60 navigation_buttons[1] = toolbar_get_action_by_name("NavFor");
61
62 gtk_action_set_sensitive(navigation_buttons[0], FALSE);
63 gtk_action_set_sensitive(navigation_buttons[1], FALSE);
64 }
65
66
navqueue_free(void)67 void navqueue_free(void)
68 {
69 while (! g_queue_is_empty(navigation_queue))
70 {
71 g_free(g_queue_pop_tail(navigation_queue));
72 }
73 g_queue_free(navigation_queue);
74 }
75
76
adjust_buttons(void)77 static void adjust_buttons(void)
78 {
79 if (g_queue_get_length(navigation_queue) < 2)
80 {
81 gtk_action_set_sensitive(navigation_buttons[0], FALSE);
82 gtk_action_set_sensitive(navigation_buttons[1], FALSE);
83 return;
84 }
85 if (nav_queue_pos == 0)
86 {
87 gtk_action_set_sensitive(navigation_buttons[0], TRUE);
88 gtk_action_set_sensitive(navigation_buttons[1], FALSE);
89 return;
90 }
91 /* forward should be sensitive since where not at the start */
92 gtk_action_set_sensitive(navigation_buttons[1], TRUE);
93
94 /* back should be sensitive if there's a place to go back to */
95 (nav_queue_pos < g_queue_get_length(navigation_queue) - 1) ?
96 gtk_action_set_sensitive(navigation_buttons[0], TRUE) :
97 gtk_action_set_sensitive(navigation_buttons[0], FALSE);
98 }
99
100
101 static gboolean
queue_pos_matches(guint queue_pos,const gchar * fname,gint pos)102 queue_pos_matches(guint queue_pos, const gchar *fname, gint pos)
103 {
104 if (queue_pos < g_queue_get_length(navigation_queue))
105 {
106 filepos *fpos = g_queue_peek_nth(navigation_queue, queue_pos);
107
108 return (utils_str_equal(fpos->file, fname) && fpos->pos == pos);
109 }
110 return FALSE;
111 }
112
113
add_new_position(const gchar * utf8_filename,gint pos)114 static void add_new_position(const gchar *utf8_filename, gint pos)
115 {
116 filepos *npos;
117 guint i;
118
119 if (queue_pos_matches(nav_queue_pos, utf8_filename, pos))
120 return; /* prevent duplicates */
121
122 npos = g_new0(filepos, 1);
123 npos->file = utf8_filename;
124 npos->pos = pos;
125
126 /* if we've jumped to a new position from inside the queue rather than going forward */
127 if (nav_queue_pos > 0)
128 {
129 for (i = 0; i < nav_queue_pos; i++)
130 {
131 g_free(g_queue_pop_head(navigation_queue));
132 }
133 nav_queue_pos = 0;
134 }
135 g_queue_push_head(navigation_queue, npos);
136 adjust_buttons();
137 }
138
139
140 /**
141 * Adds old file position and new file position to the navqueue, then goes to the new position.
142 *
143 * @param old_doc The document of the previous position, if set as invalid (@c NULL) then no old
144 * position is set
145 * @param new_doc The document of the new position, must be valid.
146 * @param line the line number of the new position. It is counted with 1 as the first line, not 0.
147 *
148 * @return @c TRUE if the cursor has changed the position to @a line or @c FALSE otherwise.
149 **/
150 GEANY_API_SYMBOL
navqueue_goto_line(GeanyDocument * old_doc,GeanyDocument * new_doc,gint line)151 gboolean navqueue_goto_line(GeanyDocument *old_doc, GeanyDocument *new_doc, gint line)
152 {
153 gint pos;
154
155 g_return_val_if_fail(old_doc == NULL || old_doc->is_valid, FALSE);
156 g_return_val_if_fail(DOC_VALID(new_doc), FALSE);
157 g_return_val_if_fail(line >= 1, FALSE);
158
159 pos = sci_get_position_from_line(new_doc->editor->sci, line - 1);
160
161 /* first add old file position */
162 if (old_doc != NULL && old_doc->file_name)
163 {
164 gint cur_pos = sci_get_current_position(old_doc->editor->sci);
165
166 add_new_position(old_doc->file_name, cur_pos);
167 }
168
169 /* now add new file position */
170 if (new_doc->file_name)
171 {
172 add_new_position(new_doc->file_name, pos);
173 }
174
175 return editor_goto_pos(new_doc->editor, pos, TRUE);
176 }
177
178
goto_file_pos(const gchar * file,gint pos)179 static gboolean goto_file_pos(const gchar *file, gint pos)
180 {
181 GeanyDocument *doc = document_find_by_filename(file);
182
183 if (doc == NULL)
184 return FALSE;
185
186 return editor_goto_pos(doc->editor, pos, TRUE);
187 }
188
189
navqueue_go_back(void)190 void navqueue_go_back(void)
191 {
192 filepos *fprev;
193 GeanyDocument *doc = document_get_current();
194
195 /* If the navqueue is currently at some position A, but the actual cursor is at some other
196 * place B, we should add B to the navqueue, so that (1) we go back to A, not to the next
197 * item in the queue; and (2) we can later restore B by going forward.
198 * (If A = B, add_new_position will ignore it.) */
199 if (doc)
200 {
201 if (doc->file_name)
202 add_new_position(doc->file_name, sci_get_current_position(doc->editor->sci));
203 }
204 else
205 /* see also https://github.com/geany/geany/pull/1537 */
206 g_warning("Attempted navigation when nothing is open");
207
208 /* return if theres no place to go back to */
209 if (g_queue_is_empty(navigation_queue) ||
210 nav_queue_pos >= g_queue_get_length(navigation_queue) - 1)
211 return;
212
213 /* jump back */
214 fprev = g_queue_peek_nth(navigation_queue, nav_queue_pos + 1);
215 if (goto_file_pos(fprev->file, fprev->pos))
216 {
217 nav_queue_pos++;
218 }
219 else
220 {
221 /** TODO: add option to re open the file */
222 g_free(g_queue_pop_nth(navigation_queue, nav_queue_pos + 1));
223 }
224 adjust_buttons();
225 }
226
227
navqueue_go_forward(void)228 void navqueue_go_forward(void)
229 {
230 filepos *fnext;
231
232 if (nav_queue_pos < 1 ||
233 nav_queue_pos >= g_queue_get_length(navigation_queue))
234 return;
235
236 /* jump forward */
237 fnext = g_queue_peek_nth(navigation_queue, nav_queue_pos - 1);
238 if (goto_file_pos(fnext->file, fnext->pos))
239 {
240 nav_queue_pos--;
241 }
242 else
243 {
244 /** TODO: add option to re open the file */
245 g_free(g_queue_pop_nth(navigation_queue, nav_queue_pos - 1));
246 }
247
248 adjust_buttons();
249 }
250
251
find_by_filename(gconstpointer a,gconstpointer b)252 static gint find_by_filename(gconstpointer a, gconstpointer b)
253 {
254 if (utils_str_equal(((const filepos*)a)->file, (const gchar*) b))
255 return 0;
256 else
257 return 1;
258 }
259
260
261 /* Remove all elements with the given filename */
navqueue_remove_file(const gchar * filename)262 void navqueue_remove_file(const gchar *filename)
263 {
264 GList *match;
265
266 if (filename == NULL)
267 return;
268
269 while ((match = g_queue_find_custom(navigation_queue, filename, find_by_filename)))
270 {
271 g_free(match->data);
272 g_queue_delete_link(navigation_queue, match);
273 }
274
275 adjust_buttons();
276 }
277