Return GdkColor via out parameter rather than return value
[geany-mirror.git] / src / utils.c
blob0a03a68f0efa1ae4898ae9a6671049c8364a8833
1 /*
2 * utils.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 * General utility functions, non-GTK related.
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
30 #include "utils.h"
32 #include "app.h"
33 #include "dialogs.h"
34 #include "document.h"
35 #include "prefs.h"
36 #include "prefix.h"
37 #include "sciwrappers.h"
38 #include "spawn.h"
39 #include "support.h"
40 #include "tm_source_file.h" // for tm_get_real_path()
41 #include "templates.h"
42 #include "ui_utils.h"
43 #include "win32.h"
44 #include "osx.h"
46 #include <stdlib.h>
47 #include <ctype.h>
48 #include <math.h>
49 #include <unistd.h>
50 #include <string.h>
51 #include <errno.h>
52 #include <stdarg.h>
54 #ifdef HAVE_SYS_STAT_H
55 # include <sys/stat.h>
56 #endif
57 #ifdef HAVE_SYS_TYPES_H
58 # include <sys/types.h>
59 #endif
61 #include <glib/gstdio.h>
62 #include <gio/gio.h>
65 /**
66 * Tries to open the given URI in a browser.
67 * On Windows, the system's default browser is opened.
68 * On non-Windows systems, the browser command set in the preferences dialog is used. In case
69 * that fails or it is unset, the user is asked to correct or fill it.
71 * @param uri The URI to open in the web browser.
73 * @since 0.16
74 **/
75 GEANY_API_SYMBOL
76 void utils_open_browser(const gchar *uri)
78 #ifdef G_OS_WIN32
79 g_return_if_fail(uri != NULL);
80 win32_open_browser(uri);
81 #else
82 gchar *argv[2] = { (gchar *) uri, NULL };
84 g_return_if_fail(uri != NULL);
86 while (!spawn_async(NULL, tool_prefs.browser_cmd, argv, NULL, NULL, NULL))
88 gchar *new_cmd = dialogs_show_input(_("Select Browser"), GTK_WINDOW(main_widgets.window),
89 _("Failed to spawn the configured browser command. "
90 "Please correct it or enter another one."),
91 tool_prefs.browser_cmd);
93 if (new_cmd == NULL) /* user canceled */
94 break;
96 SETPTR(tool_prefs.browser_cmd, new_cmd);
98 #endif
102 /* taken from anjuta, to determine the EOL mode of the file */
103 gint utils_get_line_endings(const gchar* buffer, gsize size)
105 gsize i;
106 guint cr, lf, crlf, max_mode;
107 gint mode;
109 cr = lf = crlf = 0;
111 for (i = 0; i < size ; i++)
113 if (buffer[i] == 0x0a)
115 /* LF */
116 lf++;
118 else if (buffer[i] == 0x0d)
120 if (i >= (size - 1))
122 /* Last char, CR */
123 cr++;
125 else
127 if (buffer[i + 1] != 0x0a)
129 /* CR */
130 cr++;
132 else
134 /* CRLF */
135 crlf++;
137 i++;
142 /* Vote for the maximum */
143 mode = SC_EOL_LF;
144 max_mode = lf;
145 if (crlf > max_mode)
147 mode = SC_EOL_CRLF;
148 max_mode = crlf;
150 if (cr > max_mode)
152 mode = SC_EOL_CR;
153 max_mode = cr;
156 return mode;
160 gboolean utils_isbrace(gchar c, gboolean include_angles)
162 switch (c)
164 case '<':
165 case '>':
166 return include_angles;
168 case '(':
169 case ')':
170 case '{':
171 case '}':
172 case '[':
173 case ']': return TRUE;
174 default: return FALSE;
179 gboolean utils_is_opening_brace(gchar c, gboolean include_angles)
181 switch (c)
183 case '<':
184 return include_angles;
186 case '(':
187 case '{':
188 case '[': return TRUE;
189 default: return FALSE;
195 * Writes @a text into a file named @a filename.
196 * If the file doesn't exist, it will be created.
197 * If it already exists, it will be overwritten.
199 * @warning You should use @c g_file_set_contents() instead if you don't need
200 * file permissions and other metadata to be preserved, as that always handles
201 * disk exhaustion safely.
203 * @param filename The filename of the file to write, in locale encoding.
204 * @param text The text to write into the file.
206 * @return 0 if the file was successfully written, otherwise the @c errno of the
207 * failed operation is returned.
209 GEANY_API_SYMBOL
210 gint utils_write_file(const gchar *filename, const gchar *text)
212 g_return_val_if_fail(filename != NULL, ENOENT);
213 g_return_val_if_fail(text != NULL, EINVAL);
215 if (file_prefs.use_safe_file_saving)
217 GError *error = NULL;
218 if (! g_file_set_contents(filename, text, -1, &error))
220 geany_debug("%s: could not write to file %s (%s)", G_STRFUNC, filename, error->message);
221 g_error_free(error);
222 return EIO;
225 else
227 FILE *fp;
228 gsize bytes_written, len;
229 gboolean fail = FALSE;
231 if (filename == NULL)
232 return ENOENT;
234 len = strlen(text);
235 errno = 0;
236 fp = g_fopen(filename, "w");
237 if (fp == NULL)
238 fail = TRUE;
239 else
241 bytes_written = fwrite(text, sizeof(gchar), len, fp);
243 if (len != bytes_written)
245 fail = TRUE;
246 geany_debug(
247 "utils_write_file(): written only %"G_GSIZE_FORMAT" bytes, had to write %"G_GSIZE_FORMAT" bytes to %s",
248 bytes_written, len, filename);
250 if (fclose(fp) != 0)
251 fail = TRUE;
253 if (fail)
255 geany_debug("utils_write_file(): could not write to file %s (%s)",
256 filename, g_strerror(errno));
257 return FALLBACK(errno, EIO);
260 return 0;
264 /** Searches backward through @a size bytes looking for a '<'.
265 * @param sel .
266 * @param size .
267 * @return @nullable The tag name (newly allocated) or @c NULL if no opening tag was found.
269 GEANY_API_SYMBOL
270 gchar *utils_find_open_xml_tag(const gchar sel[], gint size)
272 const gchar *cur, *begin;
273 gsize len;
275 cur = utils_find_open_xml_tag_pos(sel, size);
276 if (cur == NULL)
277 return NULL;
279 cur++; /* skip the bracket */
280 begin = cur;
281 while (strchr(":_-.", *cur) || isalnum(*cur))
282 cur++;
284 len = (gsize)(cur - begin);
285 return len ? g_strndup(begin, len) : NULL;
289 /** Searches backward through @a size bytes looking for a '<'.
290 * @param sel .
291 * @param size .
292 * @return @nullable pointer to '<' of the found opening tag within @a sel, or @c NULL if no opening tag was found.
294 GEANY_API_SYMBOL
295 const gchar *utils_find_open_xml_tag_pos(const gchar sel[], gint size)
297 /* stolen from anjuta and modified */
298 const gchar *begin, *cur;
300 if (G_UNLIKELY(size < 3))
301 { /* Smallest tag is "<p>" which is 3 characters */
302 return NULL;
304 begin = &sel[0];
305 cur = &sel[size - 1];
307 /* Skip to the character before the closing brace */
308 while (cur > begin)
310 if (*cur == '>')
311 break;
312 --cur;
314 --cur;
315 /* skip whitespace */
316 while (cur > begin && isspace(*cur))
317 cur--;
318 if (*cur == '/')
319 return NULL; /* we found a short tag which doesn't need to be closed */
320 while (cur > begin)
322 if (*cur == '<')
323 break;
324 /* exit immediately if such non-valid XML/HTML is detected, e.g. "<script>if a >" */
325 else if (*cur == '>')
326 break;
327 --cur;
330 /* if the found tag is an opening, not a closing tag or empty <> */
331 if (*cur == '<' && *(cur + 1) != '/' && *(cur + 1) != '>')
332 return cur;
334 return NULL;
338 /* Returns true if tag_name is a self-closing tag */
339 gboolean utils_is_short_html_tag(const gchar *tag_name)
341 const gchar names[][20] = {
342 "area",
343 "base",
344 "basefont", /* < or not < */
345 "br",
346 "col",
347 "command",
348 "embed",
349 "frame",
350 "hr",
351 "img",
352 "input",
353 "keygen",
354 "link",
355 "meta",
356 "param",
357 "source",
358 "track",
359 "wbr"
362 if (tag_name)
364 if (bsearch(tag_name, names, G_N_ELEMENTS(names), 20,
365 (GCompareFunc)g_ascii_strcasecmp))
366 return TRUE;
368 return FALSE;
372 const gchar *utils_get_eol_name(gint eol_mode)
374 switch (eol_mode)
376 case SC_EOL_CRLF: return _("Windows (CRLF)"); break;
377 case SC_EOL_CR: return _("Classic Mac (CR)"); break;
378 default: return _("Unix (LF)"); break;
383 const gchar *utils_get_eol_short_name(gint eol_mode)
385 switch (eol_mode)
387 case SC_EOL_CRLF: return _("CRLF"); break;
388 case SC_EOL_CR: return _("CR"); break;
389 default: return _("LF"); break;
394 const gchar *utils_get_eol_char(gint eol_mode)
396 switch (eol_mode)
398 case SC_EOL_CRLF: return "\r\n"; break;
399 case SC_EOL_CR: return "\r"; break;
400 default: return "\n"; break;
405 /* Converts line endings to @a target_eol_mode. */
406 void utils_ensure_same_eol_characters(GString *string, gint target_eol_mode)
408 const gchar *eol_str = utils_get_eol_char(target_eol_mode);
410 /* first convert data to LF only */
411 utils_string_replace_all(string, "\r\n", "\n");
412 utils_string_replace_all(string, "\r", "\n");
414 if (target_eol_mode == SC_EOL_LF)
415 return;
417 /* now convert to desired line endings */
418 utils_string_replace_all(string, "\n", eol_str);
422 gboolean utils_atob(const gchar *str)
424 if (G_UNLIKELY(str == NULL))
425 return FALSE;
426 else if (strcmp(str, "TRUE") == 0 || strcmp(str, "true") == 0)
427 return TRUE;
428 return FALSE;
432 /* NULL-safe version of g_path_is_absolute(). */
433 gboolean utils_is_absolute_path(const gchar *path)
435 if (G_UNLIKELY(EMPTY(path)))
436 return FALSE;
438 return g_path_is_absolute(path);
442 /* Skips root if path is absolute, do nothing otherwise.
443 * This is a relative-safe version of g_path_skip_root().
445 const gchar *utils_path_skip_root(const gchar *path)
447 const gchar *path_relative;
449 path_relative = g_path_skip_root(path);
451 return (path_relative != NULL) ? path_relative : path;
455 /* Convert a fractional @a val in the range [0, 1] to a whole value in the range [0, @a factor].
456 * In particular, this is used for converting a @c GdkColor to the "#RRGGBB" format in a way that
457 * agrees with GTK+, so the "#RRGGBB" in the color picker is the same "#RRGGBB" that is inserted
458 * into the document. See https://github.com/geany/geany/issues/1527
460 gdouble utils_scale_round(gdouble val, gdouble factor)
462 val = floor(val * factor + 0.5);
463 val = MAX(val, 0);
464 val = MIN(val, factor);
466 return val;
470 /* like g_utf8_strdown() but if @str is not valid UTF8, convert it from locale first.
471 * returns NULL on charset conversion failure */
472 static gchar *utf8_strdown(const gchar *str)
474 gchar *down;
476 if (g_utf8_validate(str, -1, NULL))
477 down = g_utf8_strdown(str, -1);
478 else
480 down = g_locale_to_utf8(str, -1, NULL, NULL, NULL);
481 if (down)
482 SETPTR(down, g_utf8_strdown(down, -1));
485 return down;
490 * A replacement function for g_strncasecmp() to compare strings case-insensitive.
491 * It converts both strings into lowercase using g_utf8_strdown() and then compare
492 * both strings using strcmp().
493 * This is not completely accurate regarding locale-specific case sorting rules
494 * but seems to be a good compromise between correctness and performance.
496 * The input strings should be in UTF-8 or locale encoding.
498 * @param s1 @nullable Pointer to first string or @c NULL.
499 * @param s2 @nullable Pointer to second string or @c NULL.
501 * @return an integer less than, equal to, or greater than zero if @a s1 is found, respectively,
502 * to be less than, to match, or to be greater than @a s2.
504 * @since 0.16
506 GEANY_API_SYMBOL
507 gint utils_str_casecmp(const gchar *s1, const gchar *s2)
509 gchar *tmp1, *tmp2;
510 gint result;
512 g_return_val_if_fail(s1 != NULL, 1);
513 g_return_val_if_fail(s2 != NULL, -1);
515 /* ensure strings are UTF-8 and lowercase */
516 tmp1 = utf8_strdown(s1);
517 if (! tmp1)
518 return 1;
519 tmp2 = utf8_strdown(s2);
520 if (! tmp2)
522 g_free(tmp1);
523 return -1;
526 /* compare */
527 result = strcmp(tmp1, tmp2);
529 g_free(tmp1);
530 g_free(tmp2);
531 return result;
536 * Truncates the input string to a given length.
537 * Characters are removed from the middle of the string, so the start and the end of string
538 * won't change.
540 * @param string Input string.
541 * @param truncate_length The length in characters of the resulting string.
543 * @return A copy of @a string which is truncated to @a truncate_length characters,
544 * should be freed when no longer needed.
546 * @since 0.17
548 /* This following function is taken from Gedit. */
549 GEANY_API_SYMBOL
550 gchar *utils_str_middle_truncate(const gchar *string, guint truncate_length)
552 GString *truncated;
553 guint length;
554 guint n_chars;
555 guint num_left_chars;
556 guint right_offset;
557 guint delimiter_length;
558 const gchar *delimiter = "\342\200\246";
560 g_return_val_if_fail(string != NULL, NULL);
562 length = strlen(string);
564 g_return_val_if_fail(g_utf8_validate(string, length, NULL), NULL);
566 /* It doesn't make sense to truncate strings to less than the size of the delimiter plus 2
567 * characters (one on each side) */
568 delimiter_length = g_utf8_strlen(delimiter, -1);
569 if (truncate_length < (delimiter_length + 2))
570 return g_strdup(string);
572 n_chars = g_utf8_strlen(string, length);
574 /* Make sure the string is not already small enough. */
575 if (n_chars <= truncate_length)
576 return g_strdup (string);
578 /* Find the 'middle' where the truncation will occur. */
579 num_left_chars = (truncate_length - delimiter_length) / 2;
580 right_offset = n_chars - truncate_length + num_left_chars + delimiter_length;
582 truncated = g_string_new_len(string, g_utf8_offset_to_pointer(string, num_left_chars) - string);
583 g_string_append(truncated, delimiter);
584 g_string_append(truncated, g_utf8_offset_to_pointer(string, right_offset));
586 return g_string_free(truncated, FALSE);
591 * @c NULL-safe string comparison. Returns @c TRUE if both @a a and @a b are @c NULL
592 * or if @a a and @a b refer to valid strings which are equal.
594 * @param a @nullable Pointer to first string or @c NULL.
595 * @param b @nullable Pointer to second string or @c NULL.
597 * @return @c TRUE if @a a equals @a b, else @c FALSE.
599 GEANY_API_SYMBOL
600 gboolean utils_str_equal(const gchar *a, const gchar *b)
602 /* (taken from libexo from os-cillation) */
603 if (a == NULL && b == NULL) return TRUE;
604 else if (a == NULL || b == NULL) return FALSE;
606 return strcmp(a, b) == 0;
611 * Removes the extension from @a filename and return the result in a newly allocated string.
613 * @param filename The filename to operate on.
615 * @return A newly-allocated string, should be freed when no longer needed.
617 GEANY_API_SYMBOL
618 gchar *utils_remove_ext_from_filename(const gchar *filename)
620 gchar *last_dot;
621 gchar *result;
622 gsize len;
624 g_return_val_if_fail(filename != NULL, NULL);
626 last_dot = strrchr(filename, '.');
627 if (! last_dot)
628 return g_strdup(filename);
630 len = (gsize) (last_dot - filename);
631 result = g_malloc(len + 1);
632 memcpy(result, filename, len);
633 result[len] = 0;
635 return result;
639 gchar utils_brace_opposite(gchar ch)
641 switch (ch)
643 case '(': return ')';
644 case ')': return '(';
645 case '[': return ']';
646 case ']': return '[';
647 case '{': return '}';
648 case '}': return '{';
649 case '<': return '>';
650 case '>': return '<';
651 default: return '\0';
656 /* Checks whether the given file can be written. locale_filename is expected in locale encoding.
657 * Returns 0 if it can be written, otherwise it returns errno */
658 gint utils_is_file_writable(const gchar *locale_filename)
660 gchar *file;
661 gint ret;
663 if (! g_file_test(locale_filename, G_FILE_TEST_EXISTS) &&
664 ! g_file_test(locale_filename, G_FILE_TEST_IS_DIR))
665 /* get the file's directory to check for write permission if it doesn't yet exist */
666 file = g_path_get_dirname(locale_filename);
667 else
668 file = g_strdup(locale_filename);
670 #ifdef G_OS_WIN32
671 /* use _waccess on Windows, access() doesn't accept special characters */
672 ret = win32_check_write_permission(file);
673 #else
675 /* access set also errno to "FILE NOT FOUND" even if locale_filename is writeable, so use
676 * errno only when access() explicitly returns an error */
677 if (access(file, R_OK | W_OK) != 0)
678 ret = errno;
679 else
680 ret = 0;
681 #endif
682 g_free(file);
683 return ret;
687 /* Replaces all occurrences of needle in haystack with replacement.
688 * Warning: *haystack must be a heap address; it may be freed and reassigned.
689 * Note: utils_string_replace_all() will always be faster when @a replacement is longer
690 * than @a needle.
691 * All strings have to be NULL-terminated.
692 * See utils_string_replace_all() for details. */
693 void utils_str_replace_all(gchar **haystack, const gchar *needle, const gchar *replacement)
695 GString *str;
697 g_return_if_fail(*haystack != NULL);
699 str = g_string_new(*haystack);
701 g_free(*haystack);
702 utils_string_replace_all(str, needle, replacement);
704 *haystack = g_string_free(str, FALSE);
708 gint utils_strpos(const gchar *haystack, const gchar *needle)
710 const gchar *sub;
712 if (! *needle)
713 return -1;
715 sub = strstr(haystack, needle);
716 if (! sub)
717 return -1;
719 return sub - haystack;
724 * Retrieves a formatted date/time string from strftime().
725 * This function should be preferred to directly calling strftime() since this function
726 * works on UTF-8 encoded strings.
728 * @param format The format string to pass to strftime(3). See the strftime(3)
729 * documentation for details, in UTF-8 encoding.
730 * @param time_to_use @nullable The date/time to use, in time_t format or @c NULL to use the current time.
732 * @return A newly-allocated string, should be freed when no longer needed.
734 * @since 0.16
736 GEANY_API_SYMBOL
737 gchar *utils_get_date_time(const gchar *format, time_t *time_to_use)
739 const struct tm *tm;
740 static gchar date[1024];
741 gchar *locale_format;
742 gsize len;
744 g_return_val_if_fail(format != NULL, NULL);
746 if (! g_utf8_validate(format, -1, NULL))
748 locale_format = g_locale_from_utf8(format, -1, NULL, NULL, NULL);
749 if (locale_format == NULL)
750 return NULL;
752 else
753 locale_format = g_strdup(format);
755 if (time_to_use != NULL)
756 tm = localtime(time_to_use);
757 else
759 time_t tp = time(NULL);
760 tm = localtime(&tp);
763 len = strftime(date, 1024, locale_format, tm);
764 g_free(locale_format);
765 if (len == 0)
766 return NULL;
768 if (! g_utf8_validate(date, len, NULL))
769 return g_locale_to_utf8(date, len, NULL, NULL, NULL);
770 else
771 return g_strdup(date);
775 gchar *utils_get_initials(const gchar *name)
777 gint i = 1, j = 1;
778 gchar *initials = g_malloc0(5);
780 initials[0] = name[0];
781 while (name[i] != '\0' && j < 4)
783 if (name[i] == ' ' && name[i + 1] != ' ')
785 initials[j++] = name[i + 1];
787 i++;
789 return initials;
794 * Wraps g_key_file_get_integer() to add a default value argument.
796 * @param config A GKeyFile object.
797 * @param section The group name to look in for the key.
798 * @param key The key to find.
799 * @param default_value The default value which will be returned when @a section or @a key
800 * don't exist.
802 * @return The value associated with @a key as an integer, or the given default value if the value
803 * could not be retrieved.
805 GEANY_API_SYMBOL
806 gint utils_get_setting_integer(GKeyFile *config, const gchar *section, const gchar *key,
807 const gint default_value)
809 gint tmp;
810 GError *error = NULL;
812 g_return_val_if_fail(config, default_value);
814 tmp = g_key_file_get_integer(config, section, key, &error);
815 if (error)
817 g_error_free(error);
818 return default_value;
820 return tmp;
825 * Wraps g_key_file_get_boolean() to add a default value argument.
827 * @param config A GKeyFile object.
828 * @param section The group name to look in for the key.
829 * @param key The key to find.
830 * @param default_value The default value which will be returned when @c section or @c key
831 * don't exist.
833 * @return The value associated with @a key as a boolean, or the given default value if the value
834 * could not be retrieved.
836 GEANY_API_SYMBOL
837 gboolean utils_get_setting_boolean(GKeyFile *config, const gchar *section, const gchar *key,
838 const gboolean default_value)
840 gboolean tmp;
841 GError *error = NULL;
843 g_return_val_if_fail(config, default_value);
845 tmp = g_key_file_get_boolean(config, section, key, &error);
846 if (error)
848 g_error_free(error);
849 return default_value;
851 return tmp;
856 * Wraps g_key_file_get_string() to add a default value argument.
858 * @param config A GKeyFile object.
859 * @param section The group name to look in for the key.
860 * @param key The key to find.
861 * @param default_value The default value which will be returned when @a section or @a key
862 * don't exist.
864 * @return A newly allocated string, either the value for @a key or a copy of the given
865 * default value if it could not be retrieved.
867 GEANY_API_SYMBOL
868 gchar *utils_get_setting_string(GKeyFile *config, const gchar *section, const gchar *key,
869 const gchar *default_value)
871 gchar *tmp;
873 g_return_val_if_fail(config, g_strdup(default_value));
875 tmp = g_key_file_get_string(config, section, key, NULL);
876 if (!tmp)
878 return g_strdup(default_value);
880 return tmp;
884 gchar *utils_get_hex_from_color(GdkColor *color)
886 g_return_val_if_fail(color != NULL, NULL);
888 return g_strdup_printf("#%02X%02X%02X",
889 (guint) (utils_scale_round(color->red / 65535.0, 255)),
890 (guint) (utils_scale_round(color->green / 65535.0, 255)),
891 (guint) (utils_scale_round(color->blue / 65535.0, 255)));
895 /* Get directory from current file in the notebook.
896 * Returns dir string that should be freed or NULL, depending on whether current file is valid.
897 * Returned string is in UTF-8 encoding */
898 gchar *utils_get_current_file_dir_utf8(void)
900 GeanyDocument *doc = document_get_current();
902 if (doc != NULL)
904 /* get current filename */
905 const gchar *cur_fname = doc->file_name;
907 if (cur_fname != NULL)
909 /* get folder part from current filename */
910 return g_path_get_dirname(cur_fname); /* returns "." if no path */
914 return NULL; /* no file open */
918 /* very simple convenience function */
919 void utils_beep(void)
921 if (prefs.beep_on_errors)
922 gdk_beep();
926 /* taken from busybox, thanks */
927 gchar *utils_make_human_readable_str(guint64 size, gulong block_size,
928 gulong display_unit)
930 /* The code will adjust for additional (appended) units. */
931 static const gchar zero_and_units[] = { '0', 0, 'K', 'M', 'G', 'T' };
932 static const gchar fmt[] = "%Lu %c%c";
933 static const gchar fmt_tenths[] = "%Lu.%d %c%c";
935 guint64 val;
936 gint frac;
937 const gchar *u;
938 const gchar *f;
940 u = zero_and_units;
941 f = fmt;
942 frac = 0;
944 val = size * block_size;
945 if (val == 0)
946 return g_strdup(u);
948 if (display_unit)
950 val += display_unit/2; /* Deal with rounding. */
951 val /= display_unit; /* Don't combine with the line above!!! */
953 else
955 ++u;
956 while ((val >= 1024) && (u < zero_and_units + sizeof(zero_and_units) - 1))
958 f = fmt_tenths;
959 ++u;
960 frac = ((((gint)(val % 1024)) * 10) + (1024 / 2)) / 1024;
961 val /= 1024;
963 if (frac >= 10)
964 { /* We need to round up here. */
965 ++val;
966 frac = 0;
970 /* If f==fmt then 'frac' and 'u' are ignored. */
971 return g_strdup_printf(f, val, frac, *u, 'b');
975 /* converts a color representation using gdk_color_parse(), with additional
976 * support of the "0x" prefix as a synonym for "#" */
977 gboolean utils_parse_color(const gchar *spec, GdkColor *color)
979 gchar buf[64] = {0};
981 g_return_val_if_fail(spec != NULL, -1);
983 if (spec[0] == '0' && (spec[1] == 'x' || spec[1] == 'X'))
985 /* convert to # format for GDK to understand it */
986 buf[0] = '#';
987 strncpy(buf + 1, spec + 2, sizeof(buf) - 2);
988 spec = buf;
991 return gdk_color_parse(spec, color);
995 /* converts a GdkColor to the packed 24 bits BGR format, as understood by Scintilla
996 * returns a 24 bits BGR color, or -1 on failure */
997 gint utils_color_to_bgr(const GdkColor *c)
999 g_return_val_if_fail(c != NULL, -1);
1000 return (c->red / 256) | ((c->green / 256) << 8) | ((c->blue / 256) << 16);
1004 /* parses @p spec using utils_parse_color() and convert it to 24 bits BGR using
1005 * utils_color_to_bgr() */
1006 gint utils_parse_color_to_bgr(const gchar *spec)
1008 GdkColor color;
1009 if (utils_parse_color(spec, &color))
1010 return utils_color_to_bgr(&color);
1011 else
1012 return -1;
1016 /* Returns: newly allocated string with the current time formatted HH:MM:SS. */
1017 gchar *utils_get_current_time_string(void)
1019 const time_t tp = time(NULL);
1020 const struct tm *tmval = localtime(&tp);
1021 gchar *result = g_malloc0(9);
1023 strftime(result, 9, "%H:%M:%S", tmval);
1024 result[8] = '\0';
1025 return result;
1029 GIOChannel *utils_set_up_io_channel(
1030 gint fd, GIOCondition cond, gboolean nblock, GIOFunc func, gpointer data)
1032 GIOChannel *ioc;
1033 /*const gchar *encoding;*/
1035 #ifdef G_OS_WIN32
1036 ioc = g_io_channel_win32_new_fd(fd);
1037 #else
1038 ioc = g_io_channel_unix_new(fd);
1039 #endif
1041 if (nblock)
1042 g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
1044 g_io_channel_set_encoding(ioc, NULL, NULL);
1046 if (! g_get_charset(&encoding))
1047 { // hope this works reliably
1048 GError *error = NULL;
1049 g_io_channel_set_encoding(ioc, encoding, &error);
1050 if (error)
1052 geany_debug("%s: %s", G_STRFUNC, error->message);
1053 g_error_free(error);
1054 return ioc;
1058 /* "auto-close" ;-) */
1059 g_io_channel_set_close_on_unref(ioc, TRUE);
1061 g_io_add_watch(ioc, cond, func, data);
1062 g_io_channel_unref(ioc);
1064 return ioc;
1068 /* Contributed by Stefan Oltmanns, thanks.
1069 * Replaces \\, \r, \n, \t and \uXXX by their real counterparts.
1070 * keep_backslash is used for regex strings to leave '\\' and '\?' in place */
1071 gboolean utils_str_replace_escape(gchar *string, gboolean keep_backslash)
1073 gsize i, j, len;
1074 guint unicodechar;
1076 g_return_val_if_fail(string != NULL, FALSE);
1078 j = 0;
1079 len = strlen(string);
1080 for (i = 0; i < len; i++)
1082 if (string[i]=='\\')
1084 if (i++ >= strlen(string))
1086 return FALSE;
1088 switch (string[i])
1090 case '\\':
1091 if (keep_backslash)
1092 string[j++] = '\\';
1093 string[j] = '\\';
1094 break;
1095 case 'n':
1096 string[j] = '\n';
1097 break;
1098 case 'r':
1099 string[j] = '\r';
1100 break;
1101 case 't':
1102 string[j] = '\t';
1103 break;
1104 #if 0
1105 case 'x': /* Warning: May produce illegal utf-8 string! */
1106 i += 2;
1107 if (i >= strlen(string))
1109 return FALSE;
1111 if (isdigit(string[i - 1])) string[j] = string[i - 1] - 48;
1112 else if (isxdigit(string[i - 1])) string[j] = tolower(string[i - 1])-87;
1113 else return FALSE;
1114 string[j] <<= 4;
1115 if (isdigit(string[i])) string[j] |= string[i] - 48;
1116 else if (isxdigit(string[i])) string[j] |= tolower(string[i])-87;
1117 else return FALSE;
1118 break;
1119 #endif
1120 case 'u':
1122 i += 2;
1123 if (i >= strlen(string))
1125 return FALSE;
1127 if (isdigit(string[i - 1])) unicodechar = string[i - 1] - 48;
1128 else if (isxdigit(string[i - 1])) unicodechar = tolower(string[i - 1])-87;
1129 else return FALSE;
1130 unicodechar <<= 4;
1131 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1132 else if (isxdigit(string[i])) unicodechar |= tolower(string[i])-87;
1133 else return FALSE;
1134 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1135 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1137 i += 2;
1138 unicodechar <<= 8;
1139 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1140 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1141 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1142 else unicodechar |= tolower(string[i])-87;
1144 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1145 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1147 i += 2;
1148 unicodechar <<= 8;
1149 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1150 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1151 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1152 else unicodechar |= tolower(string[i])-87;
1154 if (unicodechar < 0x80)
1156 string[j] = unicodechar;
1158 else if (unicodechar < 0x800)
1160 string[j] = (unsigned char) ((unicodechar >> 6) | 0xC0);
1161 j++;
1162 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1164 else if (unicodechar < 0x10000)
1166 string[j] = (unsigned char) ((unicodechar >> 12) | 0xE0);
1167 j++;
1168 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1169 j++;
1170 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1172 else if (unicodechar < 0x110000) /* more chars are not allowed in unicode */
1174 string[j] = (unsigned char) ((unicodechar >> 18) | 0xF0);
1175 j++;
1176 string[j] = (unsigned char) (((unicodechar >> 12) & 0x3F) | 0x80);
1177 j++;
1178 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1179 j++;
1180 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1182 else
1184 return FALSE;
1186 break;
1188 default:
1189 /* unnecessary escapes are allowed */
1190 if (keep_backslash)
1191 string[j++] = '\\';
1192 string[j] = string[i];
1195 else
1197 string[j] = string[i];
1199 j++;
1201 while (j < i)
1203 string[j] = 0;
1204 j++;
1206 return TRUE;
1210 /* Wraps a string in place, replacing a space with a newline character.
1211 * wrapstart is the minimum position to start wrapping or -1 for default */
1212 gboolean utils_wrap_string(gchar *string, gint wrapstart)
1214 gchar *pos, *linestart;
1215 gboolean ret = FALSE;
1217 if (wrapstart < 0)
1218 wrapstart = 80;
1220 for (pos = linestart = string; *pos != '\0'; pos++)
1222 if (pos - linestart >= wrapstart && *pos == ' ')
1224 *pos = '\n';
1225 linestart = pos;
1226 ret = TRUE;
1229 return ret;
1234 * Converts the given UTF-8 encoded string into locale encoding.
1235 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1237 * @param utf8_text UTF-8 encoded text.
1239 * @return The converted string in locale encoding, or a copy of the input string if conversion
1240 * failed. Should be freed with g_free(). If @a utf8_text is @c NULL, @c NULL is returned.
1242 GEANY_API_SYMBOL
1243 gchar *utils_get_locale_from_utf8(const gchar *utf8_text)
1245 #ifdef G_OS_WIN32
1246 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1247 * which would result in wrongly converted strings */
1248 return g_strdup(utf8_text);
1249 #else
1250 gchar *locale_text;
1252 if (! utf8_text)
1253 return NULL;
1254 locale_text = g_locale_from_utf8(utf8_text, -1, NULL, NULL, NULL);
1255 if (locale_text == NULL)
1256 locale_text = g_strdup(utf8_text);
1257 return locale_text;
1258 #endif
1263 * Converts the given string (in locale encoding) into UTF-8 encoding.
1264 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1266 * @param locale_text Text in locale encoding.
1268 * @return The converted string in UTF-8 encoding, or a copy of the input string if conversion
1269 * failed. Should be freed with g_free(). If @a locale_text is @c NULL, @c NULL is returned.
1271 GEANY_API_SYMBOL
1272 gchar *utils_get_utf8_from_locale(const gchar *locale_text)
1274 #ifdef G_OS_WIN32
1275 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1276 * which would result in wrongly converted strings */
1277 return g_strdup(locale_text);
1278 #else
1279 gchar *utf8_text;
1281 if (! locale_text)
1282 return NULL;
1283 utf8_text = g_locale_to_utf8(locale_text, -1, NULL, NULL, NULL);
1284 if (utf8_text == NULL)
1285 utf8_text = g_strdup(locale_text);
1286 return utf8_text;
1287 #endif
1291 /* Pass pointers to free after arg_count.
1292 * The last argument must be NULL as an extra check that arg_count is correct. */
1293 void utils_free_pointers(gsize arg_count, ...)
1295 va_list a;
1296 gsize i;
1297 gpointer ptr;
1299 va_start(a, arg_count);
1300 for (i = 0; i < arg_count; i++)
1302 ptr = va_arg(a, gpointer);
1303 g_free(ptr);
1305 ptr = va_arg(a, gpointer);
1306 if (ptr)
1307 g_warning("Wrong arg_count!");
1308 va_end(a);
1312 /* currently unused */
1313 #if 0
1314 /* Creates a string array deep copy of a series of non-NULL strings.
1315 * The first argument is nothing special.
1316 * The list must be ended with NULL.
1317 * If first is NULL, NULL is returned. */
1318 gchar **utils_strv_new(const gchar *first, ...)
1320 gsize strvlen, i;
1321 va_list args;
1322 gchar *str;
1323 gchar **strv;
1325 g_return_val_if_fail(first != NULL, NULL);
1327 strvlen = 1; /* for first argument */
1329 /* count other arguments */
1330 va_start(args, first);
1331 for (; va_arg(args, gchar*) != NULL; strvlen++);
1332 va_end(args);
1334 strv = g_new(gchar*, strvlen + 1); /* +1 for NULL terminator */
1335 strv[0] = g_strdup(first);
1337 va_start(args, first);
1338 for (i = 1; str = va_arg(args, gchar*), str != NULL; i++)
1340 strv[i] = g_strdup(str);
1342 va_end(args);
1344 strv[i] = NULL;
1345 return strv;
1347 #endif
1351 * Creates a directory if it doesn't already exist.
1352 * Creates intermediate parent directories as needed, too.
1353 * The permissions of the created directory are set 0700.
1355 * @param path The path of the directory to create, in locale encoding.
1356 * @param create_parent_dirs Whether to create intermediate parent directories if necessary.
1358 * @return 0 if the directory was successfully created, otherwise the @c errno of the
1359 * failed operation is returned.
1361 GEANY_API_SYMBOL
1362 gint utils_mkdir(const gchar *path, gboolean create_parent_dirs)
1364 gint mode = 0700;
1365 gint result;
1367 if (path == NULL || strlen(path) == 0)
1368 return EFAULT;
1370 result = (create_parent_dirs) ? g_mkdir_with_parents(path, mode) : g_mkdir(path, mode);
1371 if (result != 0)
1372 return errno;
1373 return 0;
1378 * Gets a list of files from the specified directory.
1379 * Locale encoding is expected for @a path and used for the file list. The list and the data
1380 * in the list should be freed after use, e.g.:
1381 * @code
1382 * g_slist_foreach(list, (GFunc) g_free, NULL);
1383 * g_slist_free(list); @endcode
1385 * @note If you don't need a list you should use the foreach_dir() macro instead -
1386 * it's more efficient.
1388 * @param path The path of the directory to scan, in locale encoding.
1389 * @param full_path Whether to include the full path for each filename in the list. Obviously this
1390 * will use more memory.
1391 * @param sort Whether to sort alphabetically (UTF-8 safe).
1392 * @param error The location for storing a possible error, or @c NULL.
1394 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL if
1395 * no files were found. The list and its data should be freed when no longer needed.
1396 * @see utils_get_file_list().
1398 GEANY_API_SYMBOL
1399 GSList *utils_get_file_list_full(const gchar *path, gboolean full_path, gboolean sort, GError **error)
1401 GSList *list = NULL;
1402 GDir *dir;
1403 const gchar *filename;
1405 if (error)
1406 *error = NULL;
1407 g_return_val_if_fail(path != NULL, NULL);
1409 dir = g_dir_open(path, 0, error);
1410 if (dir == NULL)
1411 return NULL;
1413 foreach_dir(filename, dir)
1415 list = g_slist_prepend(list, full_path ?
1416 g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL) : g_strdup(filename));
1418 g_dir_close(dir);
1419 /* sorting last is quicker than on insertion */
1420 if (sort)
1421 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1422 return list;
1427 * Gets a sorted list of files from the specified directory.
1428 * Locale encoding is expected for @a path and used for the file list. The list and the data
1429 * in the list should be freed after use, e.g.:
1430 * @code
1431 * g_slist_foreach(list, (GFunc) g_free, NULL);
1432 * g_slist_free(list); @endcode
1434 * @param path The path of the directory to scan, in locale encoding.
1435 * @param length The location to store the number of non-@c NULL data items in the list,
1436 * unless @c NULL.
1437 * @param error The location for storing a possible error, or @c NULL.
1439 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL
1440 * if no files were found. The list and its data should be freed when no longer needed.
1441 * @see utils_get_file_list_full().
1443 GEANY_API_SYMBOL
1444 GSList *utils_get_file_list(const gchar *path, guint *length, GError **error)
1446 GSList *list = utils_get_file_list_full(path, FALSE, TRUE, error);
1448 if (length)
1449 *length = g_slist_length(list);
1450 return list;
1454 /* returns TRUE if any letter in str is a capital, FALSE otherwise. Should be Unicode safe. */
1455 gboolean utils_str_has_upper(const gchar *str)
1457 gunichar c;
1459 if (EMPTY(str) || ! g_utf8_validate(str, -1, NULL))
1460 return FALSE;
1462 while (*str != '\0')
1464 c = g_utf8_get_char(str);
1465 /* check only letters and stop once the first non-capital was found */
1466 if (g_unichar_isalpha(c) && g_unichar_isupper(c))
1467 return TRUE;
1468 /* FIXME don't write a const string */
1469 str = g_utf8_next_char(str);
1471 return FALSE;
1475 /* end can be -1 for haystack->len.
1476 * returns: position of found text or -1. */
1477 gint utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
1479 gint pos;
1481 g_return_val_if_fail(haystack != NULL, -1);
1482 if (haystack->len == 0)
1483 return -1;
1485 g_return_val_if_fail(start >= 0, -1);
1486 if (start >= (gint)haystack->len)
1487 return -1;
1489 g_return_val_if_fail(!EMPTY(needle), -1);
1491 if (end < 0)
1492 end = haystack->len;
1494 pos = utils_strpos(haystack->str + start, needle);
1495 if (pos == -1)
1496 return -1;
1498 pos += start;
1499 if (pos >= end)
1500 return -1;
1501 return pos;
1505 /* Replaces @len characters from offset @a pos.
1506 * len can be -1 to replace the remainder of @a str.
1507 * returns: pos + strlen(replace). */
1508 gint utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
1510 g_string_erase(str, pos, len);
1511 if (replace)
1513 g_string_insert(str, pos, replace);
1514 pos += strlen(replace);
1516 return pos;
1521 * Replaces all occurrences of @a needle in @a haystack with @a replace.
1522 * As of Geany 0.16, @a replace can match @a needle, so the following will work:
1523 * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
1525 * @param haystack The input string to operate on. This string is modified in place.
1526 * @param needle The string which should be replaced.
1527 * @param replace The replacement for @a needle.
1529 * @return Number of replacements made.
1531 GEANY_API_SYMBOL
1532 guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
1534 guint count = 0;
1535 gint pos = 0;
1536 gsize needle_length = strlen(needle);
1538 while (1)
1540 pos = utils_string_find(haystack, pos, -1, needle);
1542 if (pos == -1)
1543 break;
1545 pos = utils_string_replace(haystack, pos, needle_length, replace);
1546 count++;
1548 return count;
1553 * Replaces only the first occurrence of @a needle in @a haystack
1554 * with @a replace.
1555 * For details, see utils_string_replace_all().
1557 * @param haystack The input string to operate on. This string is modified in place.
1558 * @param needle The string which should be replaced.
1559 * @param replace The replacement for @a needle.
1561 * @return Number of replacements made.
1563 * @since 0.16
1565 GEANY_API_SYMBOL
1566 guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace)
1568 gint pos = utils_string_find(haystack, 0, -1, needle);
1570 if (pos == -1)
1571 return 0;
1573 utils_string_replace(haystack, pos, strlen(needle), replace);
1574 return 1;
1578 /* Similar to g_regex_replace but allows matching a subgroup.
1579 * match_num: which match to replace, 0 for whole match.
1580 * literal: FALSE to interpret escape sequences in @a replace.
1581 * returns: number of replacements.
1582 * bug: replaced text can affect matching of ^ or \b */
1583 guint utils_string_regex_replace_all(GString *haystack, GRegex *regex,
1584 guint match_num, const gchar *replace, gboolean literal)
1586 GMatchInfo *minfo;
1587 guint ret = 0;
1588 gint start = 0;
1590 g_assert(literal); /* escapes not implemented yet */
1591 g_return_val_if_fail(replace, 0);
1593 /* ensure haystack->str is not null */
1594 if (haystack->len == 0)
1595 return 0;
1597 /* passing a start position makes G_REGEX_MATCH_NOTBOL automatic */
1598 while (g_regex_match_full(regex, haystack->str, -1, start, 0, &minfo, NULL))
1600 gint end, len;
1602 g_match_info_fetch_pos(minfo, match_num, &start, &end);
1603 len = end - start;
1604 utils_string_replace(haystack, start, len, replace);
1605 ret++;
1607 /* skip past whole match */
1608 g_match_info_fetch_pos(minfo, 0, NULL, &end);
1609 start = end - len + strlen(replace);
1610 g_match_info_free(minfo);
1612 g_match_info_free(minfo);
1613 return ret;
1617 /* Get project or default startup directory (if set), or NULL. */
1618 const gchar *utils_get_default_dir_utf8(void)
1620 if (app->project && !EMPTY(app->project->base_path))
1622 return app->project->base_path;
1625 if (!EMPTY(prefs.default_open_path))
1627 return prefs.default_open_path;
1629 return NULL;
1634 * Wraps @c spawn_sync(), which see.
1636 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1637 * @param argv The child's argument vector.
1638 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1639 * @param flags Ignored.
1640 * @param child_setup @girskip Ignored.
1641 * @param user_data @girskip Ignored.
1642 * @param std_out @out @optional The return location for child output, or @c NULL.
1643 * @param std_err @out @optional The return location for child error messages, or @c NULL.
1644 * @param exit_status @out @optional The child exit status, as returned by waitpid(), or @c NULL.
1645 * @param error The return location for error or @c NULL.
1647 * @return @c TRUE on success, @c FALSE if an error was set.
1649 GEANY_API_SYMBOL
1650 gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1651 GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out,
1652 gchar **std_err, gint *exit_status, GError **error)
1654 GString *output = std_out ? g_string_new(NULL) : NULL;
1655 GString *errors = std_err ? g_string_new(NULL) : NULL;
1656 gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
1658 if (std_out)
1659 *std_out = g_string_free(output, !result);
1661 if (std_err)
1662 *std_err = g_string_free(errors, !result);
1664 return result;
1669 * Wraps @c spawn_async(), which see.
1671 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1672 * @param argv The child's argument vector.
1673 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1674 * @param flags Ignored.
1675 * @param child_setup @girskip Ignored.
1676 * @param user_data Ignored.
1677 * @param child_pid @out @nullable The return location for child process ID, or @c NULL.
1678 * @param error The return location for error or @c NULL.
1680 * @return @c TRUE on success, @c FALSE if an error was set.
1682 GEANY_API_SYMBOL
1683 gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1684 GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
1685 GError **error)
1687 return spawn_async(dir, NULL, argv, env, child_pid, error);
1691 /* Returns "file:///" on Windows, "file://" everywhere else */
1692 const gchar *utils_get_uri_file_prefix(void)
1694 #ifdef G_OS_WIN32
1695 return "file:///";
1696 #else
1697 return "file://";
1698 #endif
1702 /* Retrieves the path for the given URI.
1703 * It returns:
1704 * - the path which was determined by g_filename_from_uri() or GIO
1705 * - NULL if the URI is non-local and gvfs-fuse is not installed
1706 * - a new copy of 'uri' if it is not an URI. */
1707 gchar *utils_get_path_from_uri(const gchar *uri)
1709 gchar *locale_filename;
1711 g_return_val_if_fail(uri != NULL, NULL);
1713 if (! utils_is_uri(uri))
1714 return g_strdup(uri);
1716 /* this will work only for 'file://' URIs */
1717 locale_filename = g_filename_from_uri(uri, NULL, NULL);
1718 /* g_filename_from_uri() failed, so we probably have a non-local URI */
1719 if (locale_filename == NULL)
1721 GFile *file = g_file_new_for_uri(uri);
1722 locale_filename = g_file_get_path(file);
1723 g_object_unref(file);
1724 if (locale_filename == NULL)
1726 geany_debug("The URI '%s' could not be resolved to a local path. This means "
1727 "that the URI is invalid or that you don't have gvfs-fuse installed.", uri);
1731 return locale_filename;
1735 gboolean utils_is_uri(const gchar *uri)
1737 g_return_val_if_fail(uri != NULL, FALSE);
1739 return (strstr(uri, "://") != NULL);
1743 /* path should be in locale encoding */
1744 gboolean utils_is_remote_path(const gchar *path)
1746 g_return_val_if_fail(path != NULL, FALSE);
1748 /* if path is an URI and it doesn't start "file://", we take it as remote */
1749 if (utils_is_uri(path) && strncmp(path, "file:", 5) != 0)
1750 return TRUE;
1752 #ifndef G_OS_WIN32
1754 static gchar *fuse_path = NULL;
1755 static gsize len = 0;
1757 if (G_UNLIKELY(fuse_path == NULL))
1759 fuse_path = g_build_filename(g_get_home_dir(), ".gvfs", NULL);
1760 len = strlen(fuse_path);
1762 /* Comparing the file path against a hardcoded path is not the most elegant solution
1763 * but for now it is better than nothing. Ideally, g_file_new_for_path() should create
1764 * proper GFile objects for Fuse paths, but it only does in future GVFS
1765 * versions (gvfs 1.1.1). */
1766 return (strncmp(path, fuse_path, len) == 0);
1768 #endif
1770 return FALSE;
1774 /* Remove all relative and untidy elements from the path of @a filename.
1775 * @param filename must be a valid absolute path.
1776 * @see utils_get_real_path() - also resolves links. */
1777 void utils_tidy_path(gchar *filename)
1779 GString *str;
1780 const gchar *needle;
1781 gboolean preserve_double_backslash = FALSE;
1783 g_return_if_fail(g_path_is_absolute(filename));
1785 str = g_string_new(filename);
1787 if (str->len >= 2 && strncmp(str->str, "\\\\", 2) == 0)
1788 preserve_double_backslash = TRUE;
1790 #ifdef G_OS_WIN32
1791 /* using MSYS we can get Unix-style separators */
1792 utils_string_replace_all(str, "/", G_DIR_SEPARATOR_S);
1793 #endif
1794 /* replace "/./" and "//" */
1795 utils_string_replace_all(str, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1796 utils_string_replace_all(str, G_DIR_SEPARATOR_S G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1798 if (preserve_double_backslash)
1799 g_string_prepend(str, "\\");
1801 /* replace "/../" */
1802 needle = G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S;
1803 while (1)
1805 const gchar *c = strstr(str->str, needle);
1806 if (c == NULL)
1807 break;
1808 else
1810 gssize pos, sub_len;
1812 pos = c - str->str;
1813 if (pos <= 3)
1814 break; /* bad path */
1816 /* replace "/../" */
1817 g_string_erase(str, pos, strlen(needle));
1818 g_string_insert_c(str, pos, G_DIR_SEPARATOR);
1820 /* search for last "/" before found "/../" */
1821 c = g_strrstr_len(str->str, pos, G_DIR_SEPARATOR_S);
1822 sub_len = pos - (c - str->str);
1823 if (! c)
1824 break; /* bad path */
1826 pos = c - str->str; /* position of previous "/" */
1827 g_string_erase(str, pos, sub_len);
1830 if (str->len <= strlen(filename))
1831 memcpy(filename, str->str, str->len + 1);
1832 else
1833 g_warn_if_reached();
1834 g_string_free(str, TRUE);
1839 * Removes characters from a string, in place.
1841 * @param string String to search.
1842 * @param chars Characters to remove.
1844 * @return @a string - return value is only useful when nesting function calls, e.g.:
1845 * @code str = utils_str_remove_chars(g_strdup("f_o_o"), "_"); @endcode
1847 * @see @c g_strdelimit.
1849 GEANY_API_SYMBOL
1850 gchar *utils_str_remove_chars(gchar *string, const gchar *chars)
1852 const gchar *r;
1853 gchar *w = string;
1855 g_return_val_if_fail(string, NULL);
1856 if (G_UNLIKELY(EMPTY(chars)))
1857 return string;
1859 foreach_str(r, string)
1861 if (!strchr(chars, *r))
1862 *w++ = *r;
1864 *w = 0x0;
1865 return string;
1869 /* Gets list of sorted filenames with no path and no duplicates from user and system config */
1870 GSList *utils_get_config_files(const gchar *subdir)
1872 gchar *path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, subdir, NULL);
1873 GSList *list = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1874 GSList *syslist, *node;
1876 if (!list)
1878 utils_mkdir(path, FALSE);
1880 SETPTR(path, g_build_path(G_DIR_SEPARATOR_S, app->datadir, subdir, NULL));
1881 syslist = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1882 /* merge lists */
1883 list = g_slist_concat(list, syslist);
1885 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1886 /* remove duplicates (next to each other after sorting) */
1887 foreach_slist(node, list)
1889 if (node->next && utils_str_equal(node->next->data, node->data))
1891 GSList *old = node->next;
1893 g_free(old->data);
1894 node->next = old->next;
1895 g_slist_free1(old);
1898 g_free(path);
1899 return list;
1903 /* Suffix can be NULL or a string which should be appended to the Help URL like
1904 * an anchor link, e.g. "#some_anchor". */
1905 gchar *utils_get_help_url(const gchar *suffix)
1907 gchar *uri;
1908 const gchar *uri_file_prefix = utils_get_uri_file_prefix();
1909 gint skip = strlen(uri_file_prefix);
1911 uri = g_strconcat(uri_file_prefix, app->docdir, "/index.html", NULL);
1912 #ifdef G_OS_WIN32
1913 g_strdelimit(uri, "\\", '/'); /* replace '\\' by '/' */
1914 #endif
1916 if (! g_file_test(uri + skip, G_FILE_TEST_IS_REGULAR))
1917 { /* fall back to online documentation if it is not found on the hard disk */
1918 g_free(uri);
1919 uri = g_strconcat(GEANY_HOMEPAGE, "manual/", VERSION, "/index.html", NULL);
1922 if (suffix != NULL)
1924 SETPTR(uri, g_strconcat(uri, suffix, NULL));
1927 return uri;
1931 static gboolean str_in_array(const gchar **haystack, const gchar *needle)
1933 const gchar **p;
1935 for (p = haystack; *p != NULL; ++p)
1937 if (utils_str_equal(*p, needle))
1938 return TRUE;
1940 return FALSE;
1945 * Copies the current environment into a new array.
1946 * @a exclude_vars is a @c NULL-terminated array of variable names which should be not copied.
1947 * All further arguments are key, value pairs of variables which should be added to
1948 * the environment.
1950 * The argument list must be @c NULL-terminated.
1952 * @param exclude_vars @c NULL-terminated array of variable names to exclude.
1953 * @param first_varname Name of the first variable to copy into the new array.
1954 * @param ... Key-value pairs of variable names and values, @c NULL-terminated.
1956 * @return @transfer{full} The new environment array. Use @c g_strfreev() to free it.
1958 GEANY_API_SYMBOL
1959 gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...)
1961 gchar **result;
1962 gchar **p;
1963 gchar **env;
1964 va_list args;
1965 const gchar *key, *value;
1966 guint n, o;
1968 /* count the additional variables */
1969 va_start(args, first_varname);
1970 for (o = 1; va_arg(args, gchar*) != NULL; o++);
1971 va_end(args);
1972 /* the passed arguments should be even (key, value pairs) */
1973 g_return_val_if_fail(o % 2 == 0, NULL);
1975 o /= 2;
1977 /* get all the environ variables */
1978 env = g_listenv();
1980 /* create an array large enough to hold the new environment */
1981 n = g_strv_length(env);
1982 /* 'n + o + 1' could leak a little bit when exclude_vars is set */
1983 result = g_new(gchar *, n + o + 1);
1985 /* copy the environment */
1986 for (n = 0, p = env; *p != NULL; ++p)
1988 /* copy the variable */
1989 value = g_getenv(*p);
1990 if (G_LIKELY(value != NULL))
1992 /* skip excluded variables */
1993 if (exclude_vars != NULL && str_in_array(exclude_vars, *p))
1994 continue;
1996 result[n++] = g_strconcat(*p, "=", value, NULL);
1999 g_strfreev(env);
2001 /* now add additional variables */
2002 va_start(args, first_varname);
2003 key = first_varname;
2004 value = va_arg(args, gchar*);
2005 while (key != NULL)
2007 result[n++] = g_strconcat(key, "=", value, NULL);
2009 key = va_arg(args, gchar*);
2010 if (key == NULL)
2011 break;
2012 value = va_arg(args, gchar*);
2014 va_end(args);
2016 result[n] = NULL;
2018 return result;
2022 /* Joins @a first and @a second into a new string vector, freeing the originals.
2023 * The original contents are reused. */
2024 gchar **utils_strv_join(gchar **first, gchar **second)
2026 gchar **strv;
2027 gchar **rptr, **wptr;
2029 if (!first)
2030 return second;
2031 if (!second)
2032 return first;
2034 strv = g_new0(gchar*, g_strv_length(first) + g_strv_length(second) + 1);
2035 wptr = strv;
2037 foreach_strv(rptr, first)
2038 *wptr++ = *rptr;
2039 foreach_strv(rptr, second)
2040 *wptr++ = *rptr;
2042 g_free(first);
2043 g_free(second);
2044 return strv;
2048 /* Try to parse a date using g_date_set_parse(). It doesn't take any format hint,
2049 * obviously g_date_set_parse() uses some magic.
2050 * The returned GDate object must be freed. */
2051 GDate *utils_parse_date(const gchar *input)
2053 GDate *date = g_date_new();
2055 g_date_set_parse(date, input);
2057 if (g_date_valid(date))
2058 return date;
2060 g_date_free(date);
2061 return NULL;
2065 gchar *utils_parse_and_format_build_date(const gchar *input)
2067 gchar date_buf[255];
2068 GDate *date = utils_parse_date(input);
2070 if (date != NULL)
2072 g_date_strftime(date_buf, sizeof(date_buf), GEANY_TEMPLATES_FORMAT_DATE, date);
2073 g_date_free(date);
2074 return g_strdup(date_buf);
2077 return g_strdup(input);
2081 gchar *utils_get_user_config_dir(void)
2083 #ifdef G_OS_WIN32
2084 return win32_get_user_config_dir();
2085 #else
2086 return g_build_filename(g_get_user_config_dir(), "geany", NULL);
2087 #endif
2091 static gboolean is_osx_bundle(void)
2093 #ifdef MAC_INTEGRATION
2094 gchar *bundle_id = gtkosx_application_get_bundle_id();
2095 if (bundle_id)
2097 g_free(bundle_id);
2098 return TRUE;
2100 #endif
2101 return FALSE;
2105 const gchar *utils_resource_dir(GeanyResourceDirType type)
2107 static const gchar *resdirs[RESOURCE_DIR_COUNT] = {NULL};
2109 if (!resdirs[RESOURCE_DIR_DATA])
2111 #ifdef G_OS_WIN32
2112 gchar *prefix = win32_get_installation_dir();
2114 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "data", NULL);
2115 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2116 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2117 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2118 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2119 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2120 g_free(prefix);
2121 #else
2122 if (is_osx_bundle())
2124 # ifdef MAC_INTEGRATION
2125 gchar *prefix = gtkosx_application_get_resource_path();
2127 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "share", "geany", NULL);
2128 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2129 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2130 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2131 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2132 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2133 g_free(prefix);
2134 # endif
2136 else
2138 resdirs[RESOURCE_DIR_DATA] = g_build_filename(GEANY_DATADIR, "geany", NULL);
2139 resdirs[RESOURCE_DIR_ICON] = g_build_filename(GEANY_DATADIR, "icons", NULL);
2140 resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL);
2141 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL);
2142 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL);
2143 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(GEANY_LIBEXECDIR, "geany", NULL);
2145 #endif
2148 return resdirs[type];
2152 void utils_start_new_geany_instance(const gchar *doc_path)
2154 const gchar *command = is_osx_bundle() ? "open" : "geany";
2155 gchar *exec_path = g_find_program_in_path(command);
2157 if (exec_path)
2159 GError *err = NULL;
2160 const gchar *argv[6]; // max args + 1
2161 gint argc = 0;
2163 argv[argc++] = exec_path;
2164 if (is_osx_bundle())
2166 argv[argc++] = "-n";
2167 argv[argc++] = "-a";
2168 argv[argc++] = "Geany";
2169 argv[argc++] = doc_path;
2171 else
2173 argv[argc++] = "-i";
2174 argv[argc++] = doc_path;
2176 argv[argc] = NULL;
2178 if (!utils_spawn_async(NULL, (gchar**) argv, NULL, 0, NULL, NULL, NULL, &err))
2180 g_printerr("Unable to open new window: %s", err->message);
2181 g_error_free(err);
2183 g_free(exec_path);
2185 else
2186 g_printerr("Unable to find 'geany'");
2191 * Get a link-dereferenced, absolute version of a file name.
2193 * This is similar to the POSIX `realpath` function when passed a
2194 * @c NULL argument.
2196 * @warning This function suffers the same problems as the POSIX
2197 * function `realpath()`, namely that it's impossible to determine
2198 * a suitable size for the returned buffer, and so it's limited to a
2199 * maximum of `PATH_MAX`.
2201 * @param file_name The file name to get the real path of.
2203 * @return A newly-allocated string containing the real path which
2204 * should be freed with `g_free()` when no longer needed, or @c NULL
2205 * if the real path cannot be obtained.
2207 * @since 1.32 (API 235)
2209 GEANY_API_SYMBOL
2210 gchar *utils_get_real_path(const gchar *file_name)
2212 return tm_get_real_path(file_name);