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 &regular_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