1 /* vifm
2 * Copyright (C) 2001 Ken Steen.
3 * Copyright (C) 2011 xaizek.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 */
19
20 #include "marks.h"
21
22 #include <stddef.h> /* NULL */
23 #include <stdlib.h> /* free() */
24 #include <string.h> /* strdup() */
25 #include <time.h> /* time_t time() */
26
27 #include "cfg/config.h"
28 #include "compat/fs_limits.h"
29 #include "modes/wk.h"
30 #include "ui/fileview.h"
31 #include "ui/statusbar.h"
32 #include "ui/tabs.h"
33 #include "ui/ui.h"
34 #include "utils/fs.h"
35 #include "utils/macros.h"
36 #include "utils/path.h"
37 #include "utils/str.h"
38 #include "filelist.h"
39 #include "flist_pos.h"
40
41 static int is_valid_index(const int index);
42 static void clear_marks(mark_t marks[], int count);
43 static void reset_mark(mark_t *mark);
44 static int is_user_mark(char name);
45 static void set_mark(view_t *view, char name, const char directory[],
46 const char file[], time_t timestamp, int force);
47 static int is_mark_points_to(const mark_t *mark, const char directory[],
48 const char file[]);
49 static int navigate_to_mark(view_t *view, char name);
50 TSTATIC mark_t * get_mark_by_name(view_t *view, char name);
51 static mark_t * find_mark(view_t *view, const int index);
52 static int is_mark_valid(const mark_t *mark);
53 static int is_empty(const mark_t *mark);
54
55 /* User-writable marks. */
56 #define USER_MARKS \
57 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
58 /* Marks that can't be explicitly set by the user. */
59 #define SPECIAL_MARKS "<>'"
60
61 /* Data of regular marks. */
62 static mark_t regular_marks[NUM_REGULAR_MARKS];
63
64 const char marks_all[] = USER_MARKS SPECIAL_MARKS;
65 ARRAY_GUARD(marks_all, NUM_MARKS + 1);
66
67 /* List of special marks that can't be set manually, hence require special
68 * treatment in some cases. */
69 static const char spec_marks[] = SPECIAL_MARKS;
70 ARRAY_GUARD(spec_marks, NUM_SPECIAL_MARKS + 1);
71
72 const mark_t *
marks_by_index(view_t * view,const int index)73 marks_by_index(view_t *view, const int index)
74 {
75 return find_mark(view, index);
76 }
77
78 char
marks_resolve_index(const int index)79 marks_resolve_index(const int index)
80 {
81 if(is_valid_index(index))
82 {
83 return marks_all[index];
84 }
85 return '\0';
86 }
87
88 /* Checks whether mark index is valid. Returns non-zero if so, otherwise zero
89 * is returned. */
90 static int
is_valid_index(const int index)91 is_valid_index(const int index)
92 {
93 return index >= 0 && index < (int)ARRAY_LEN(marks_all) - 1;
94 }
95
96 int
marks_is_valid(view_t * view,const int index)97 marks_is_valid(view_t *view, const int index)
98 {
99 const char m = marks_resolve_index(index);
100 const mark_t *const mark = get_mark_by_name(view, m);
101 return is_mark_valid(mark);
102 }
103
104 int
marks_is_empty(view_t * view,const char name)105 marks_is_empty(view_t *view, const char name)
106 {
107 return is_empty(get_mark_by_name(view, name));
108 }
109
110 int
marks_is_special(const int index)111 marks_is_special(const int index)
112 {
113 return char_is_one_of(spec_marks, marks_resolve_index(index));
114 }
115
116 void
marks_clear_one(view_t * view,char name)117 marks_clear_one(view_t *view, char name)
118 {
119 reset_mark(get_mark_by_name(view, name));
120 }
121
122 void
marks_clear_all(void)123 marks_clear_all(void)
124 {
125 clear_marks(regular_marks, ARRAY_LEN(regular_marks));
126
127 int i;
128 tab_info_t tab_info;
129 for(i = 0; tabs_enum_all(i, &tab_info); ++i)
130 {
131 marks_clear_view(tab_info.view);
132 }
133 }
134
135 void
marks_clear_view(struct view_t * view)136 marks_clear_view(struct view_t *view)
137 {
138 clear_marks(view->special_marks, ARRAY_LEN(view->special_marks));
139 }
140
141 static void
clear_marks(mark_t marks[],int count)142 clear_marks(mark_t marks[], int count)
143 {
144 int i;
145 for(i = 0; i < count; ++i)
146 {
147 reset_mark(&marks[i]);
148 }
149 }
150
151 /* Frees memory allocated for mark with given index. For convenience mark can
152 * be NULL. */
153 static void
reset_mark(mark_t * mark)154 reset_mark(mark_t *mark)
155 {
156 if(mark != NULL && !is_empty(mark))
157 {
158 free(mark->directory);
159 mark->directory = NULL;
160
161 free(mark->file);
162 mark->file = NULL;
163
164 mark->timestamp = time(NULL);
165 }
166 }
167
168 int
marks_is_older(view_t * view,char name,const time_t than)169 marks_is_older(view_t *view, char name, const time_t than)
170 {
171 const mark_t *const mark = get_mark_by_name(view, name);
172 if(mark != NULL)
173 {
174 static const time_t undef_time = (time_t)-1;
175 if(mark->timestamp == undef_time || than == undef_time)
176 {
177 return mark->timestamp == undef_time;
178 }
179 return mark->timestamp < than;
180 }
181 return 1;
182 }
183
184 int
marks_set_user(view_t * view,char name,const char directory[],const char file[])185 marks_set_user(view_t *view, char name, const char directory[],
186 const char file[])
187 {
188 if(!is_user_mark(name))
189 {
190 ui_sb_msg("Invalid mark name");
191 return 1;
192 }
193
194 set_mark(view, name, directory, file, time(NULL), 0);
195 return 0;
196 }
197
198 void
marks_setup_user(view_t * view,char name,const char directory[],const char file[],time_t timestamp)199 marks_setup_user(view_t *view, char name, const char directory[],
200 const char file[], time_t timestamp)
201 {
202 if(is_user_mark(name))
203 {
204 set_mark(view, name, directory, file, timestamp, 1);
205 }
206 else
207 {
208 ui_sb_errf("Only user's marks can be loaded, but got: %c", name);
209 }
210 }
211
212 /* Checks whether given mark corresponds to mark that can be set by a user.
213 * Returns non-zero if so, otherwise zero is returned. */
214 static int
is_user_mark(char name)215 is_user_mark(char name)
216 {
217 return char_is_one_of(marks_all, name)
218 && !char_is_one_of(spec_marks, name);
219 }
220
221 void
marks_set_special(view_t * view,char name,const char directory[],const char file[])222 marks_set_special(view_t *view, char name, const char directory[],
223 const char file[])
224 {
225 if(char_is_one_of(spec_marks, name))
226 {
227 set_mark(view, name, directory, file, time(NULL), 1);
228 }
229 }
230
231 /* Sets values of the mark. The force parameter controls whether mark is
232 * updated even when it already points to the specified directory-file pair. */
233 static void
set_mark(view_t * view,char name,const char directory[],const char file[],time_t timestamp,int force)234 set_mark(view_t *view, char name, const char directory[], const char file[],
235 time_t timestamp, int force)
236 {
237 mark_t *const mark = get_mark_by_name(view, name);
238 if(mark != NULL && (force || !is_mark_points_to(mark, directory, file)))
239 {
240 reset_mark(mark);
241
242 mark->directory = strdup(directory);
243 mark->file = strdup(file);
244 mark->timestamp = timestamp;
245
246 /* Remove any trailing slashes, they might be convenient in configuration
247 * file (hence they are permitted), but shouldn't be stored internally. */
248 chosp(mark->file);
249 }
250 }
251
252 /* Checks whether given mark points to specified directory-file pair. Returns
253 * non-zero if so, otherwise zero is returned. */
254 static int
is_mark_points_to(const mark_t * mark,const char directory[],const char file[])255 is_mark_points_to(const mark_t *mark, const char directory[], const char file[])
256 {
257 return !is_empty(mark)
258 && mark->timestamp != (time_t)-1
259 && strcmp(mark->directory, directory) == 0
260 && strcmp(mark->file, file) == 0;
261 }
262
263 int
marks_find_in_view(view_t * view,char name)264 marks_find_in_view(view_t *view, char name)
265 {
266 int custom;
267 const mark_t *const mark = get_mark_by_name(view, name);
268
269 if(is_empty(mark))
270 {
271 return -1;
272 }
273
274 custom = flist_custom_active(view);
275
276 if(custom && cv_tree(view->custom.type) && strcmp(mark->file, "..") == 0)
277 {
278 return fpos_find_entry(view, mark->file, mark->directory);
279 }
280
281 if(custom)
282 {
283 dir_entry_t *entry;
284 char path[PATH_MAX + 1];
285 snprintf(path, sizeof(path), "%s/%s", mark->directory, mark->file);
286 entry = entry_from_path(view, view->dir_entry, view->list_rows, path);
287 if(entry != NULL)
288 {
289 return entry_to_pos(view, entry);
290 }
291 }
292 else if(paths_are_equal(view->curr_dir, mark->directory))
293 {
294 return fpos_find_by_name(view, mark->file);
295 }
296
297 return -1;
298 }
299
300 int
marks_goto(view_t * view,char name)301 marks_goto(view_t *view, char name)
302 {
303 switch(name)
304 {
305 case '\'':
306 navigate_back(view);
307 return 0;
308 case NC_C_c:
309 case NC_ESC:
310 fview_cursor_redraw(view);
311 return 0;
312
313 default:
314 return navigate_to_mark(view, name);
315 }
316 }
317
318 /* Navigates the view to given mark if it's valid. Returns new value for
319 * save_msg flag. */
320 static int
navigate_to_mark(view_t * view,char name)321 navigate_to_mark(view_t *view, char name)
322 {
323 const mark_t *const mark = get_mark_by_name(view, name);
324
325 if(is_mark_valid(mark))
326 {
327 if(!cfg_ch_pos_on(CHPOS_DIRMARK) || flist_custom_active(view) ||
328 !is_parent_dir(mark->file))
329 {
330 navigate_to_file(view, mark->directory, mark->file, 1);
331 }
332 else
333 {
334 navigate_to(view, mark->directory);
335 }
336 return 0;
337 }
338
339 if(!char_is_one_of(marks_all, name))
340 {
341 ui_sb_msg("Invalid mark name");
342 }
343 else if(is_empty(mark))
344 {
345 ui_sb_msg("Mark is not set");
346 }
347 else
348 {
349 ui_sb_msg("Mark is invalid");
350 }
351
352 fview_cursor_redraw(view);
353 return 1;
354 }
355
356 /* Gets mark data structure by name of a mark. Returns pointer to mark's data
357 * structure or NULL. */
358 TSTATIC mark_t *
get_mark_by_name(view_t * view,char name)359 get_mark_by_name(view_t *view, char name)
360 {
361 const char *const pos = strchr(marks_all, name);
362 return (pos == NULL) ? NULL : find_mark(view, pos - marks_all);
363 }
364
365 /* Gets mark by its index. Returns pointer to a statically allocated mark_t
366 * structure or NULL for wrong index. */
367 static mark_t *
find_mark(view_t * view,const int index)368 find_mark(view_t *view, const int index)
369 {
370 int spec_mark_index;
371
372 if(!is_valid_index(index))
373 {
374 return NULL;
375 }
376
377 if(index < NUM_REGULAR_MARKS)
378 {
379 return ®ular_marks[index];
380 }
381
382 spec_mark_index = index - NUM_REGULAR_MARKS;
383 return &view->special_marks[spec_mark_index];
384 }
385
386 /* Checks if a mark is valid (exists and points to an existing directory). For
387 * convenience mark can be NULL. Returns non-zero if so, otherwise zero is
388 * returned. */
389 static int
is_mark_valid(const mark_t * mark)390 is_mark_valid(const mark_t *mark)
391 {
392 return !is_empty(mark) && is_valid_dir(mark->directory);
393 }
394
395 int
marks_list_active(view_t * view,const char marks[],int active_marks[])396 marks_list_active(view_t *view, const char marks[], int active_marks[])
397 {
398 int i, x;
399
400 i = 0;
401 for(x = 0; x < NUM_MARKS; ++x)
402 {
403 if(!char_is_one_of(marks, marks_resolve_index(x)))
404 continue;
405 if(is_empty(marks_by_index(view, x)))
406 continue;
407 active_marks[i++] = x;
408 }
409 return i;
410 }
411
412 /* Checks whether mark specified is empty. For convenience mark can be NULL.
413 * Returns non-zero for non-empty or NULL mark, otherwise zero is returned. */
414 static int
is_empty(const mark_t * mark)415 is_empty(const mark_t *mark)
416 {
417 return mark == NULL
418 || mark->directory == NULL
419 || mark->file == NULL;
420 }
421
422 void
marks_suggest(view_t * view,mark_suggest_cb cb,int local_only)423 marks_suggest(view_t *view, mark_suggest_cb cb, int local_only)
424 {
425 int active_marks[NUM_MARKS];
426 const int count = marks_list_active(view, marks_all, active_marks);
427 int i;
428
429 for(i = 0; i < count; ++i)
430 {
431 char *descr;
432 const char *file = "";
433 const char *suffix = "";
434 const int m = active_marks[i];
435 const wchar_t mark_name[] = {
436 L'm', L'a', L'r', L'k', L':', L' ', marks_resolve_index(m), L'\0'
437 };
438 const mark_t *const mark = marks_by_index(view, m);
439
440 if(local_only && marks_find_in_view(view, marks_resolve_index(m)) == -1)
441 {
442 continue;
443 }
444
445 if(marks_is_valid(view, m) && !is_parent_dir(mark->file))
446 {
447 char path[PATH_MAX + 1];
448 file = mark->file;
449 snprintf(path, sizeof(path), "%s/%s", mark->directory, file);
450 if(is_dir(path))
451 {
452 suffix = "/";
453 }
454 }
455
456 descr = format_str("%s%s%s%s", replace_home_part(mark->directory),
457 (file[0] != '\0') ? "/" : "", file, suffix);
458
459 cb(mark_name, L"", descr);
460
461 free(descr);
462 }
463 }
464
465 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
466 /* vim: set cinoptions+=t0 filetype=c : */
467