completion.c 11.5 KB
Newer Older
Moritz Lipp's avatar
Moritz Lipp committed
1
/* See LICENSE file for license and copyright information */
2
3
4
5

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
Moritz Lipp's avatar
Moritz Lipp committed
6
#include <unistd.h>
Moritz Lipp's avatar
Moritz Lipp committed
7
#include <libgen.h>
Moritz Lipp's avatar
Moritz Lipp committed
8
#include <glib/gi18n.h>
9

Sebastian Ramacher's avatar
Sebastian Ramacher committed
10
#include "bookmarks.h"
Moritz Lipp's avatar
Moritz Lipp committed
11
#include "document.h"
12
13
#include "completion.h"
#include "utils.h"
Moritz Lipp's avatar
Moritz Lipp committed
14
#include "page.h"
15
#include "database.h"
16

17
#include <girara/session.h>
18
#include <girara/settings.h>
19
20
21
22
#include <girara/completion.h>
#include <girara/utils.h>
#include <girara/datastructures.h>

Sebastian Ramacher's avatar
Sebastian Ramacher committed
23
24
25
26
27
28
29
30
31
32
33
static int
compare_case_insensitive(const char* str1, const char* str2)
{
  char* ustr1 = g_utf8_casefold(str1, -1);
  char* ustr2 = g_utf8_casefold(str2, -1);
  int res = g_utf8_collate(ustr1, ustr2);
  g_free(ustr1);
  g_free(ustr2);
  return res;
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
34
static girara_list_t*
35
list_files(zathura_t* zathura, const char* current_path, const char* current_file,
36
           size_t current_file_length, bool is_dir, bool check_file_ext)
Sebastian Ramacher's avatar
Sebastian Ramacher committed
37
{
38
39
40
41
  if (zathura == NULL || zathura->ui.session == NULL || current_path == NULL) {
    return NULL;
  }

42
43
  girara_debug("checking files in %s", current_path);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
44
45
46
47
48
49
  /* read directory */
  GDir* dir = g_dir_open(current_path, 0, NULL);
  if (dir == NULL) {
    return NULL;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
50
  girara_list_t* res = girara_sorted_list_new2((girara_compare_function_t)compare_case_insensitive,
Moritz Lipp's avatar
Moritz Lipp committed
51
                       (girara_free_function_t)g_free);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
52

53
54
  bool show_hidden = false;
  girara_setting_get(zathura->ui.session, "show-hidden", &show_hidden);
55
56
  bool show_directories = true;
  girara_setting_get(zathura->ui.session, "show-directories", &show_directories);
57

Sebastian Ramacher's avatar
Sebastian Ramacher committed
58
59
60
61
62
  /* read files */
  char* name = NULL;
  while ((name = (char*) g_dir_read_name(dir)) != NULL) {
    char* e_name   = g_filename_display_name(name);
    if (e_name == NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
63
      goto error_free;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
64
65
    }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
66
    size_t e_length = strlen(e_name);
67
68

    if (show_hidden == false && e_name[0] == '.') {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
69
      g_free(e_name);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
70
      continue;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
71
72
    }

73
74
75
76
    if ((current_file_length > e_length) || strncmp(current_file, e_name, current_file_length)) {
      g_free(e_name);
      continue;
    }
Moritz Lipp's avatar
Moritz Lipp committed
77
78
79
80
81
82
83

    char* tmp = "/";
    if (is_dir == true || g_strcmp0(current_path, "/") == 0) {
      tmp = "";
    };

    char* full_path = g_strdup_printf("%s%s%s", current_path, tmp, e_name);
84
    g_free(e_name);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
85
86

    if (g_file_test(full_path, G_FILE_TEST_IS_DIR) == true) {
87
      if (show_directories == false) {
88
        girara_debug("ignoring %s (directory)", full_path);
89
90
91
        g_free(full_path);
        continue;
      }
92
      girara_debug("adding %s (directory)", full_path);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
93
      girara_list_append(res, full_path);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
94
    } else if (check_file_ext == false || file_valid_extension(zathura, full_path) == true) {
95
      girara_debug("adding %s (file)", full_path);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
96
97
      girara_list_append(res, full_path);
    } else {
98
      girara_debug("ignoring %s (file)", full_path);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
99
100
101
102
103
      g_free(full_path);
    }
  }

  g_dir_close(dir);
104
105
106
107
108
109
110
111
112
113

  if (girara_list_size(res) == 1) {
    char* path = girara_list_nth(res, 0);
    if (g_file_test(path, G_FILE_TEST_IS_DIR) == true) {
      char* newpath = g_strdup_printf("%s/", path);
      girara_list_clear(res);
      girara_list_append(res, newpath);
    }
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
114
  return res;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
115
116
117
118

error_free:
  girara_list_free(res);
  return NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
119
120
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
121
static girara_completion_t*
122
list_files_for_cc(zathura_t* zathura, const char* input, bool check_file_ext, int show_recent)
123
124
{
  girara_completion_t* completion  = girara_completion_init();
125
126
  girara_completion_group_t* group = girara_completion_group_create(zathura->ui.session, "files");
  girara_completion_group_t* history_group = NULL;
127

Moritz Lipp's avatar
Moritz Lipp committed
128
129
130
  gchar* path         = NULL;
  gchar* current_path = NULL;

131
  if (show_recent > 0) {
132
133
134
    history_group = girara_completion_group_create(zathura->ui.session, "recent files");
  }

135
  if (completion == NULL || group == NULL || (show_recent > 0 && history_group == NULL)) {
136
137
138
    goto error_free;
  }

Moritz Lipp's avatar
Moritz Lipp committed
139
  path = girara_fix_path(input);
Moritz Lipp's avatar
Moritz Lipp committed
140
  if (path == NULL) {
141
142
143
144
145
    goto error_free;
  }

  /* If the path does not begin with a slash we update the path with the current
   * working directory */
Moritz Lipp's avatar
Moritz Lipp committed
146
  if (strlen(path) == 0 || path[0] != '/') {
147
    long path_max;
148
149
150
151
#ifdef PATH_MAX
    path_max = PATH_MAX;
#else
    path_max = pathconf(path,_PC_PATH_MAX);
152
    if (path_max <= 0)
153
154
155
156
      path_max = 4096;
#endif

    char cwd[path_max];
Sebastian Ramacher's avatar
Sebastian Ramacher committed
157
158
159
    if (getcwd(cwd, path_max) == NULL) {
      goto error_free;
    }
160
161
162
163
164
165
166

    char* tmp_path = g_strdup_printf("%s/%s", cwd, path);

    g_free(path);
    path = tmp_path;
  }

Moritz Lipp's avatar
Moritz Lipp committed
167
  /* Append a slash if the given argument is a directory */
Moritz Lipp's avatar
Moritz Lipp committed
168
169
  bool is_dir = (path[strlen(path) - 1] == '/') ? true : false;
  if ((g_file_test(path, G_FILE_TEST_IS_DIR) == TRUE) && !is_dir) {
Moritz Lipp's avatar
Moritz Lipp committed
170
171
172
    char* tmp_path = g_strdup_printf("%s/", path);
    g_free(path);
    path = tmp_path;
Moritz Lipp's avatar
Moritz Lipp committed
173
    is_dir = true;
Moritz Lipp's avatar
Moritz Lipp committed
174
175
  }

Moritz Lipp's avatar
Moritz Lipp committed
176
177
178
179
180
181
182
  /* get current path */
  char* tmp    = g_strdup(path);
  current_path = is_dir ? g_strdup(tmp) : g_strdup(dirname(tmp));
  g_free(tmp);

  /* get current file */
  gchar* current_file     = is_dir ? "" : basename(path);
183
  const size_t current_file_length = strlen(current_file);
Moritz Lipp's avatar
Moritz Lipp committed
184

185
  /* read directory */
Moritz Lipp's avatar
Moritz Lipp committed
186
  if (g_file_test(current_path, G_FILE_TEST_IS_DIR) == TRUE) {
187
    girara_list_t* names = list_files(zathura, current_path, current_file, current_file_length, is_dir, check_file_ext);
188
    if (names == NULL) {
189
190
191
      goto error_free;
    }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
192
    GIRARA_LIST_FOREACH(names, const char*, iter, file)
Moritz Lipp's avatar
Moritz Lipp committed
193
    girara_completion_group_add_element(group, file, NULL);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
194
195
    GIRARA_LIST_FOREACH_END(names, const char*, iter, file);
    girara_list_free(names);
196
197
  }

198
  if (show_recent > 0 && zathura->database != NULL) {
199
    girara_list_t* recent_files = zathura_db_get_recent_files(zathura->database, show_recent);
200
201
202
203
    if (recent_files == NULL) {
      goto error_free;
    }

204
205
206
207
    if (girara_list_size(recent_files) != 0) {
      const size_t path_len = strlen(path);
      GIRARA_LIST_FOREACH(recent_files, const char*, iter, file)
        if (strncmp(path, file, path_len) == 0) {
208
          girara_debug("adding %s (recent file)", file);
209
210
211
212
213
214
215
216
          girara_completion_group_add_element(history_group, file, NULL);
        }
      GIRARA_LIST_FOREACH_END(recent_files, const char*, iter, file);
      girara_list_free(recent_files);
    } else {
      girara_completion_group_free(history_group);
      history_group = NULL;
    }
217
218
  }

219
  g_free(path);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
220
  g_free(current_path);
221

222
223
224
  if (history_group != NULL) {
    girara_completion_add_group(completion, history_group);
  }
225
226
227
228
229
230
231
232
233
  girara_completion_add_group(completion, group);

  return completion;

error_free:

  if (completion) {
    girara_completion_free(completion);
  }
234
235
236
  if (history_group) {
    girara_completion_group_free(history_group);
  }
237
238
239
240
  if (group) {
    girara_completion_group_free(group);
  }

Moritz Lipp's avatar
Moritz Lipp committed
241
  g_free(current_path);
242
243
244
245
  g_free(path);

  return NULL;
}
Sebastian Ramacher's avatar
Sebastian Ramacher committed
246

247
248
249
250
251
252
253
girara_completion_t*
cc_open(girara_session_t* session, const char* input)
{
  g_return_val_if_fail(session != NULL, NULL);
  g_return_val_if_fail(session->global.data != NULL, NULL);
  zathura_t* zathura = session->global.data;

254
  int show_recent = 0;
255
256
257
  girara_setting_get(zathura->ui.session, "show-recent", &show_recent);

  return list_files_for_cc(zathura, input, true, show_recent);
258
259
260
261
262
263
264
265
266
}

girara_completion_t*
cc_write(girara_session_t* session, const char* input)
{
  g_return_val_if_fail(session != NULL, NULL);
  g_return_val_if_fail(session->global.data != NULL, NULL);
  zathura_t* zathura = session->global.data;

267
  return list_files_for_cc(zathura, input, false, false);
268
269
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
270
girara_completion_t*
271
cc_bookmarks(girara_session_t* session, const char* input)
Sebastian Ramacher's avatar
Sebastian Ramacher committed
272
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
273
274
275
276
  if (input == NULL) {
    return NULL;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
277
278
279
280
281
282
283
  g_return_val_if_fail(session != NULL, NULL);
  g_return_val_if_fail(session->global.data != NULL, NULL);
  zathura_t* zathura = session->global.data;

  girara_completion_t* completion  = girara_completion_init();
  girara_completion_group_t* group = girara_completion_group_create(session, NULL);

Moritz Lipp's avatar
Moritz Lipp committed
284
285
286
287
  if (completion == NULL || group == NULL) {
    goto error_free;
  }

288
  const size_t input_length = strlen(input);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
289
  GIRARA_LIST_FOREACH(zathura->bookmarks.bookmarks, zathura_bookmark_t*, iter, bookmark)
Moritz Lipp's avatar
Moritz Lipp committed
290
291
292
293
294
  if (input_length <= strlen(bookmark->id) && !strncmp(input, bookmark->id, input_length)) {
    gchar* paged = g_strdup_printf(_("Page %d"), bookmark->page);
    girara_completion_group_add_element(group, bookmark->id, paged);
    g_free(paged);
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
295
  GIRARA_LIST_FOREACH_END(zathura->bookmarks.bookmarks, zathura_bookmark_t*, iter, bookmark);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
296

Moritz Lipp's avatar
Moritz Lipp committed
297
298
  girara_completion_add_group(completion, group);

Sebastian Ramacher's avatar
Sebastian Ramacher committed
299
  return completion;
Moritz Lipp's avatar
Moritz Lipp committed
300
301
302
303
304
305
306
307
308
309
310
311

error_free:

  if (completion) {
    girara_completion_free(completion);
  }

  if (group) {
    girara_completion_group_free(group);
  }

  return NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
312
}
Sebastian Ramacher's avatar
Sebastian Ramacher committed
313
314
315
316
317
318
319
320

girara_completion_t*
cc_export(girara_session_t* session, const char* input)
{
  g_return_val_if_fail(session != NULL, NULL);
  g_return_val_if_fail(session->global.data != NULL, NULL);
  zathura_t* zathura = session->global.data;

Moritz Lipp's avatar
Moritz Lipp committed
321
322
323
  if (input == NULL || zathura->document == NULL) {
    goto error_ret;
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
324

Moritz Lipp's avatar
Moritz Lipp committed
325
326
327
328
329
330
  girara_completion_t* completion             = NULL;
  girara_completion_group_t* attachment_group = NULL;
  girara_completion_group_t* image_group      = NULL;

  completion = girara_completion_init();
  if (completion == NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
331
332
333
    goto error_free;
  }

Moritz Lipp's avatar
Moritz Lipp committed
334
335
336
337
338
339
  attachment_group = girara_completion_group_create(session, _("Attachments"));
  if (attachment_group == NULL) {
    goto error_free;
  }

  /* add attachments */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
340
  const size_t input_length = strlen(input);
341
  girara_list_t* attachments = zathura_document_attachments_get(zathura->document, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
342
343
344
345
  if (attachments != NULL) {
    bool added = false;

    GIRARA_LIST_FOREACH(attachments, const char*, iter, attachment)
Moritz Lipp's avatar
Moritz Lipp committed
346
347
348
349
350
351
    if (input_length <= strlen(attachment) && !strncmp(input, attachment, input_length)) {
      char* attachment_string = g_strdup_printf("attachment-%s", attachment);
      girara_completion_group_add_element(attachment_group, attachment_string, NULL);
      g_free(attachment_string);
      added = true;
    }
Moritz Lipp's avatar
Moritz Lipp committed
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
    GIRARA_LIST_FOREACH_END(zathura->bookmarks.bookmarks, zathura_bookmark_t*, iter, bookmark);

    if (added == true) {
      girara_completion_add_group(completion, attachment_group);
    } else {
      girara_completion_group_free(attachment_group);
      attachment_group = NULL;
    }

    girara_list_free(attachments);
  }

  /* add images */
  image_group = girara_completion_group_create(session, _("Images"));
  if (image_group == NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
367
368
369
    goto error_free;
  }

Moritz Lipp's avatar
Moritz Lipp committed
370
371
372
373
374
375
376
  bool added = false;

  unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
  for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) {
    zathura_page_t* page = zathura_document_get_page(zathura->document, page_id);
    if (page == NULL) {
      continue;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
377
378
    }

Moritz Lipp's avatar
Moritz Lipp committed
379
380
381
    girara_list_t* images = zathura_page_images_get(page, NULL);
    if (images != NULL) {
      unsigned int image_number = 1;
Moritz Lipp's avatar
Moritz Lipp committed
382
      GIRARA_LIST_FOREACH(images, zathura_image_t*, iter, UNUSED(image))
Moritz Lipp's avatar
Moritz Lipp committed
383
384
385
      char* image_string = g_strdup_printf("image-p%d-%d", page_id + 1, image_number);
      girara_completion_group_add_element(image_group, image_string, NULL);
      g_free(image_string);
Moritz Lipp's avatar
Moritz Lipp committed
386

Moritz Lipp's avatar
Moritz Lipp committed
387
388
      added = true;
      image_number++;
Moritz Lipp's avatar
Moritz Lipp committed
389
390
391
392
393
394
395
396
397
398
399
400
      GIRARA_LIST_FOREACH_END(images, zathura_image_t*, iter, image);
      girara_list_free(images);
    }
  }

  if (added == true) {
    girara_completion_add_group(completion, image_group);
  } else {
    girara_completion_group_free(image_group);
    image_group = NULL;
  }

Sebastian Ramacher's avatar
Sebastian Ramacher committed
401
402
403
404
  return completion;

error_free:

Moritz Lipp's avatar
Moritz Lipp committed
405
  if (completion != NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
406
407
408
    girara_completion_free(completion);
  }

Moritz Lipp's avatar
Moritz Lipp committed
409
410
411
412
413
414
  if (attachment_group != NULL) {
    girara_completion_group_free(attachment_group);
  }

  if (image_group != NULL) {
    girara_completion_group_free(image_group);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
415
416
  }

Moritz Lipp's avatar
Moritz Lipp committed
417
418
error_ret:

Sebastian Ramacher's avatar
Sebastian Ramacher committed
419
420
  return NULL;
}