completion.c 11 KB
Newer Older
1
/* SPDX-License-Identifier: Zlib */
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>
8
#include <glib/gi18n.h>
9

10
#include "bookmarks.h"
11
#include "document.h"
12 13
#include "completion.h"
#include "utils.h"
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>

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;
  }

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
  /* read files */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
59 60
  const char* name = NULL;
  while ((name = g_dir_read_name(dir)) != NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
61 62
    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;
    }
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

  if (girara_list_size(res) == 1) {
    char* path = girara_list_nth(res, 0);
    if (g_file_test(path, G_FILE_TEST_IS_DIR) == true) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
108
      girara_debug("changing to directory %s", path);
109 110 111 112 113 114
      char* newpath = g_strdup_printf("%s/", path);
      girara_list_clear(res);
      girara_list_append(res, newpath);
    }
  }

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

error_free:
118
  g_dir_close(dir);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
119 120
  girara_list_free(res);
  return NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
121 122
}

123 124 125 126 127 128 129 130 131
static void
group_add_element(void* data, void* userdata)
{
  const char* element              = data;
  girara_completion_group_t* group = userdata;

  girara_completion_group_add_element(group, element, NULL);
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
132
static girara_completion_t*
133
list_files_for_cc(zathura_t* zathura, const char* input, bool check_file_ext, int show_recent)
134 135
{
  girara_completion_t* completion  = girara_completion_init();
136 137
  girara_completion_group_t* group = girara_completion_group_create(zathura->ui.session, "files");
  girara_completion_group_t* history_group = NULL;
138

Moritz Lipp's avatar
Moritz Lipp committed
139 140 141
  gchar* path         = NULL;
  gchar* current_path = NULL;

142
  if (show_recent > 0) {
143 144 145
    history_group = girara_completion_group_create(zathura->ui.session, "recent files");
  }

146
  if (completion == NULL || group == NULL || (show_recent > 0 && history_group == NULL)) {
147 148 149
    goto error_free;
  }

Moritz Lipp's avatar
Moritz Lipp committed
150
  path = girara_fix_path(input);
Moritz Lipp's avatar
Moritz Lipp committed
151
  if (path == NULL) {
152 153 154 155 156
    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
157
  if (strlen(path) == 0 || path[0] != '/') {
158 159
    char* cwd = g_get_current_dir();
    if (cwd == NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
160 161
      goto error_free;
    }
162 163

    char* tmp_path = g_strdup_printf("%s/%s", cwd, path);
164 165 166 167
    g_free(cwd);
    if (tmp_path == NULL) {
      goto error_free;
    }
168 169 170 171 172

    g_free(path);
    path = tmp_path;
  }

Moritz Lipp's avatar
Moritz Lipp committed
173
  /* Append a slash if the given argument is a directory */
Moritz Lipp's avatar
Moritz Lipp committed
174
  bool is_dir = (path[strlen(path) - 1] == '/') ? true : false;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
175
  if ((g_file_test(path, G_FILE_TEST_IS_DIR) == TRUE) && is_dir == false) {
Moritz Lipp's avatar
Moritz Lipp committed
176 177 178
    char* tmp_path = g_strdup_printf("%s/", path);
    g_free(path);
    path = tmp_path;
Moritz Lipp's avatar
Moritz Lipp committed
179
    is_dir = true;
Moritz Lipp's avatar
Moritz Lipp committed
180 181
  }

Moritz Lipp's avatar
Moritz Lipp committed
182
  /* get current path */
183
  current_path = is_dir ? g_strdup(path) : g_path_get_dirname(path);
Moritz Lipp's avatar
Moritz Lipp committed
184 185

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

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

196
    girara_list_foreach(names, group_add_element, group);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
197
    girara_list_free(names);
198 199
  }

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

206
    if (girara_list_size(recent_files) != 0) {
207
      girara_list_foreach(recent_files, group_add_element, history_group);
208 209 210 211 212
      girara_list_free(recent_files);
    } else {
      girara_completion_group_free(history_group);
      history_group = NULL;
    }
213 214
  }

215
  g_free(path);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
216
  g_free(current_path);
217

218 219 220
  if (history_group != NULL) {
    girara_completion_add_group(completion, history_group);
  }
221 222 223 224 225 226 227 228 229
  girara_completion_add_group(completion, group);

  return completion;

error_free:

  if (completion) {
    girara_completion_free(completion);
  }
230 231 232
  if (history_group) {
    girara_completion_group_free(history_group);
  }
233 234 235 236
  if (group) {
    girara_completion_group_free(group);
  }

Moritz Lipp's avatar
Moritz Lipp committed
237
  g_free(current_path);
238 239 240 241
  g_free(path);

  return NULL;
}
242

243 244 245 246 247 248 249
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;

250
  int show_recent = 0;
251 252 253
  girara_setting_get(zathura->ui.session, "show-recent", &show_recent);

  return list_files_for_cc(zathura, input, true, show_recent);
254 255 256 257 258 259 260 261 262
}

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;

263
  return list_files_for_cc(zathura, input, false, false);
264 265
}

266
girara_completion_t*
267
cc_bookmarks(girara_session_t* session, const char* input)
268
{
Sebastian Ramacher's avatar
Sebastian Ramacher committed
269 270 271 272
  if (input == NULL) {
    return NULL;
  }

273 274 275 276 277 278 279
  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
280 281 282 283
  if (completion == NULL || group == NULL) {
    goto error_free;
  }

284
  const size_t input_length = strlen(input);
285 286 287 288 289 290 291
  GIRARA_LIST_FOREACH_BODY(zathura->bookmarks.bookmarks, zathura_bookmark_t*, bookmark,
    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);
    }
  );
292

Moritz Lipp's avatar
Moritz Lipp committed
293 294
  girara_completion_add_group(completion, group);

295
  return completion;
Moritz Lipp's avatar
Moritz Lipp committed
296 297 298

error_free:

299
  if (completion != NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
300 301 302
    girara_completion_free(completion);
  }

303
  if (group != NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
304 305 306 307
    girara_completion_group_free(group);
  }

  return NULL;
308
}
Sebastian Ramacher's avatar
Sebastian Ramacher committed
309 310 311 312 313 314 315 316

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;

317 318 319
  if (input == NULL || zathura->document == NULL) {
    goto error_ret;
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
320

321 322 323 324 325 326
  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
327 328 329
    goto error_free;
  }

330 331 332 333 334 335
  attachment_group = girara_completion_group_create(session, _("Attachments"));
  if (attachment_group == NULL) {
    goto error_free;
  }

  /* add attachments */
Sebastian Ramacher's avatar
Sebastian Ramacher committed
336
  const size_t input_length = strlen(input);
337
  girara_list_t* attachments = zathura_document_attachments_get(zathura->document, NULL);
338 339 340
  if (attachments != NULL) {
    bool added = false;

341 342 343 344 345 346 347 348
    GIRARA_LIST_FOREACH_BODY(attachments, const char*, attachment,
      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;
      }
    );
349 350 351 352 353 354 355 356 357 358 359 360 361 362

    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
363 364 365
    goto error_free;
  }

366 367 368 369 370 371 372
  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
373 374
    }

375 376 377
    girara_list_t* images = zathura_page_images_get(page, NULL);
    if (images != NULL) {
      unsigned int image_number = 1;
378 379 380 381 382 383 384 385
      GIRARA_LIST_FOREACH_BODY(images, zathura_image_t*, UNUSED(image),
        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);

        added = true;
        image_number++;
      );
386 387 388 389 390 391 392 393 394 395 396
      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
397 398 399 400
  return completion;

error_free:

401
  if (completion != NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
402 403 404
    girara_completion_free(completion);
  }

405 406 407 408 409 410
  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
411 412
  }

413 414
error_ret:

Sebastian Ramacher's avatar
Sebastian Ramacher committed
415 416
  return NULL;
}