Merge pull request #1133 from techee/readme_rst
[geany-mirror.git] / src / utils.c
blobb5ea73dac559ef7e0758c1050b6e65a26d604e3d
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 "templates.h"
41 #include "ui_utils.h"
42 #include "win32.h"
43 #include "osx.h"
45 #include <stdlib.h>
46 #include <ctype.h>
47 #include <math.h>
48 #include <unistd.h>
49 #include <string.h>
50 #include <errno.h>
51 #include <stdarg.h>
53 #ifdef HAVE_SYS_STAT_H
54 # include <sys/stat.h>
55 #endif
56 #ifdef HAVE_SYS_TYPES_H
57 # include <sys/types.h>
58 #endif
60 #include <glib/gstdio.h>
61 #include <gio/gio.h>
64 /**
65 * Tries to open the given URI in a browser.
66 * On Windows, the system's default browser is opened.
67 * On non-Windows systems, the browser command set in the preferences dialog is used. In case
68 * that fails or it is unset, the user is asked to correct or fill it.
70 * @param uri The URI to open in the web browser.
72 * @since 0.16
73 **/
74 GEANY_API_SYMBOL
75 void utils_open_browser(const gchar *uri)
77 #ifdef G_OS_WIN32
78 g_return_if_fail(uri != NULL);
79 win32_open_browser(uri);
80 #else
81 gchar *argv[2] = { (gchar *) uri, NULL };
83 g_return_if_fail(uri != NULL);
85 while (!spawn_async(NULL, tool_prefs.browser_cmd, argv, NULL, NULL, NULL))
87 gchar *new_cmd = dialogs_show_input(_("Select Browser"), GTK_WINDOW(main_widgets.window),
88 _("Failed to spawn the configured browser command. "
89 "Please correct it or enter another one."),
90 tool_prefs.browser_cmd);
92 if (new_cmd == NULL) /* user canceled */
93 break;
95 SETPTR(tool_prefs.browser_cmd, new_cmd);
97 #endif
101 /* taken from anjuta, to determine the EOL mode of the file */
102 gint utils_get_line_endings(const gchar* buffer, gsize size)
104 gsize i;
105 guint cr, lf, crlf, max_mode;
106 gint mode;
108 cr = lf = crlf = 0;
110 for (i = 0; i < size ; i++)
112 if (buffer[i] == 0x0a)
114 /* LF */
115 lf++;
117 else if (buffer[i] == 0x0d)
119 if (i >= (size - 1))
121 /* Last char, CR */
122 cr++;
124 else
126 if (buffer[i + 1] != 0x0a)
128 /* CR */
129 cr++;
131 else
133 /* CRLF */
134 crlf++;
136 i++;
141 /* Vote for the maximum */
142 mode = SC_EOL_LF;
143 max_mode = lf;
144 if (crlf > max_mode)
146 mode = SC_EOL_CRLF;
147 max_mode = crlf;
149 if (cr > max_mode)
151 mode = SC_EOL_CR;
152 max_mode = cr;
155 return mode;
159 gboolean utils_isbrace(gchar c, gboolean include_angles)
161 switch (c)
163 case '<':
164 case '>':
165 return include_angles;
167 case '(':
168 case ')':
169 case '{':
170 case '}':
171 case '[':
172 case ']': return TRUE;
173 default: return FALSE;
178 gboolean utils_is_opening_brace(gchar c, gboolean include_angles)
180 switch (c)
182 case '<':
183 return include_angles;
185 case '(':
186 case '{':
187 case '[': return TRUE;
188 default: return FALSE;
194 * Writes @a text into a file named @a filename.
195 * If the file doesn't exist, it will be created.
196 * If it already exists, it will be overwritten.
198 * @warning You should use @c g_file_set_contents() instead if you don't need
199 * file permissions and other metadata to be preserved, as that always handles
200 * disk exhaustion safely.
202 * @param filename The filename of the file to write, in locale encoding.
203 * @param text The text to write into the file.
205 * @return 0 if the file was successfully written, otherwise the @c errno of the
206 * failed operation is returned.
208 GEANY_API_SYMBOL
209 gint utils_write_file(const gchar *filename, const gchar *text)
211 g_return_val_if_fail(filename != NULL, ENOENT);
212 g_return_val_if_fail(text != NULL, EINVAL);
214 if (file_prefs.use_safe_file_saving)
216 GError *error = NULL;
217 if (! g_file_set_contents(filename, text, -1, &error))
219 geany_debug("%s: could not write to file %s (%s)", G_STRFUNC, filename, error->message);
220 g_error_free(error);
221 return EIO;
224 else
226 FILE *fp;
227 gsize bytes_written, len;
228 gboolean fail = FALSE;
230 if (filename == NULL)
231 return ENOENT;
233 len = strlen(text);
234 errno = 0;
235 fp = g_fopen(filename, "w");
236 if (fp == NULL)
237 fail = TRUE;
238 else
240 bytes_written = fwrite(text, sizeof(gchar), len, fp);
242 if (len != bytes_written)
244 fail = TRUE;
245 geany_debug(
246 "utils_write_file(): written only %"G_GSIZE_FORMAT" bytes, had to write %"G_GSIZE_FORMAT" bytes to %s",
247 bytes_written, len, filename);
249 if (fclose(fp) != 0)
250 fail = TRUE;
252 if (fail)
254 geany_debug("utils_write_file(): could not write to file %s (%s)",
255 filename, g_strerror(errno));
256 return FALLBACK(errno, EIO);
259 return 0;
263 /** Searches backward through @a size bytes looking for a '<'.
264 * @param sel .
265 * @param size .
266 * @return @nullable The tag name (newly allocated) or @c NULL if no opening tag was found.
268 GEANY_API_SYMBOL
269 gchar *utils_find_open_xml_tag(const gchar sel[], gint size)
271 const gchar *cur, *begin;
272 gsize len;
274 cur = utils_find_open_xml_tag_pos(sel, size);
275 if (cur == NULL)
276 return NULL;
278 cur++; /* skip the bracket */
279 begin = cur;
280 while (strchr(":_-.", *cur) || isalnum(*cur))
281 cur++;
283 len = (gsize)(cur - begin);
284 return len ? g_strndup(begin, len) : NULL;
288 /** Searches backward through @a size bytes looking for a '<'.
289 * @param sel .
290 * @param size .
291 * @return @nullable pointer to '<' of the found opening tag within @a sel, or @c NULL if no opening tag was found.
293 GEANY_API_SYMBOL
294 const gchar *utils_find_open_xml_tag_pos(const gchar sel[], gint size)
296 /* stolen from anjuta and modified */
297 const gchar *begin, *cur;
299 if (G_UNLIKELY(size < 3))
300 { /* Smallest tag is "<p>" which is 3 characters */
301 return NULL;
303 begin = &sel[0];
304 cur = &sel[size - 1];
306 /* Skip to the character before the closing brace */
307 while (cur > begin)
309 if (*cur == '>')
310 break;
311 --cur;
313 --cur;
314 /* skip whitespace */
315 while (cur > begin && isspace(*cur))
316 cur--;
317 if (*cur == '/')
318 return NULL; /* we found a short tag which doesn't need to be closed */
319 while (cur > begin)
321 if (*cur == '<')
322 break;
323 /* exit immediately if such non-valid XML/HTML is detected, e.g. "<script>if a >" */
324 else if (*cur == '>')
325 break;
326 --cur;
329 /* if the found tag is an opening, not a closing tag or empty <> */
330 if (*cur == '<' && *(cur + 1) != '/' && *(cur + 1) != '>')
331 return cur;
333 return NULL;
337 /* Returns true if tag_name is a self-closing tag */
338 gboolean utils_is_short_html_tag(const gchar *tag_name)
340 const gchar names[][20] = {
341 "area",
342 "base",
343 "basefont", /* < or not < */
344 "br",
345 "col",
346 "command",
347 "embed",
348 "frame",
349 "hr",
350 "img",
351 "input",
352 "keygen",
353 "link",
354 "meta",
355 "param",
356 "source",
357 "track",
358 "wbr"
361 if (tag_name)
363 if (bsearch(tag_name, names, G_N_ELEMENTS(names), 20,
364 (GCompareFunc)g_ascii_strcasecmp))
365 return TRUE;
367 return FALSE;
371 const gchar *utils_get_eol_name(gint eol_mode)
373 switch (eol_mode)
375 case SC_EOL_CRLF: return _("Windows (CRLF)"); break;
376 case SC_EOL_CR: return _("Classic Mac (CR)"); break;
377 default: return _("Unix (LF)"); break;
382 const gchar *utils_get_eol_short_name(gint eol_mode)
384 switch (eol_mode)
386 case SC_EOL_CRLF: return _("CRLF"); break;
387 case SC_EOL_CR: return _("CR"); break;
388 default: return _("LF"); break;
393 const gchar *utils_get_eol_char(gint eol_mode)
395 switch (eol_mode)
397 case SC_EOL_CRLF: return "\r\n"; break;
398 case SC_EOL_CR: return "\r"; break;
399 default: return "\n"; break;
404 /* Converts line endings to @a target_eol_mode. */
405 void utils_ensure_same_eol_characters(GString *string, gint target_eol_mode)
407 const gchar *eol_str = utils_get_eol_char(target_eol_mode);
409 /* first convert data to LF only */
410 utils_string_replace_all(string, "\r\n", "\n");
411 utils_string_replace_all(string, "\r", "\n");
413 if (target_eol_mode == SC_EOL_LF)
414 return;
416 /* now convert to desired line endings */
417 utils_string_replace_all(string, "\n", eol_str);
421 gboolean utils_atob(const gchar *str)
423 if (G_UNLIKELY(str == NULL))
424 return FALSE;
425 else if (strcmp(str, "TRUE") == 0 || strcmp(str, "true") == 0)
426 return TRUE;
427 return FALSE;
431 /* NULL-safe version of g_path_is_absolute(). */
432 gboolean utils_is_absolute_path(const gchar *path)
434 if (G_UNLIKELY(EMPTY(path)))
435 return FALSE;
437 return g_path_is_absolute(path);
441 /* Skips root if path is absolute, do nothing otherwise.
442 * This is a relative-safe version of g_path_skip_root().
444 const gchar *utils_path_skip_root(const gchar *path)
446 const gchar *path_relative;
448 path_relative = g_path_skip_root(path);
450 return (path_relative != NULL) ? path_relative : path;
454 gdouble utils_scale_round(gdouble val, gdouble factor)
456 /*val = floor(val * factor + 0.5);*/
457 val = floor(val);
458 val = MAX(val, 0);
459 val = MIN(val, factor);
461 return val;
465 /* like g_utf8_strdown() but if @str is not valid UTF8, convert it from locale first.
466 * returns NULL on charset conversion failure */
467 static gchar *utf8_strdown(const gchar *str)
469 gchar *down;
471 if (g_utf8_validate(str, -1, NULL))
472 down = g_utf8_strdown(str, -1);
473 else
475 down = g_locale_to_utf8(str, -1, NULL, NULL, NULL);
476 if (down)
477 SETPTR(down, g_utf8_strdown(down, -1));
480 return down;
485 * A replacement function for g_strncasecmp() to compare strings case-insensitive.
486 * It converts both strings into lowercase using g_utf8_strdown() and then compare
487 * both strings using strcmp().
488 * This is not completely accurate regarding locale-specific case sorting rules
489 * but seems to be a good compromise between correctness and performance.
491 * The input strings should be in UTF-8 or locale encoding.
493 * @param s1 @nullable Pointer to first string or @c NULL.
494 * @param s2 @nullable Pointer to second string or @c NULL.
496 * @return an integer less than, equal to, or greater than zero if @a s1 is found, respectively,
497 * to be less than, to match, or to be greater than @a s2.
499 * @since 0.16
501 GEANY_API_SYMBOL
502 gint utils_str_casecmp(const gchar *s1, const gchar *s2)
504 gchar *tmp1, *tmp2;
505 gint result;
507 g_return_val_if_fail(s1 != NULL, 1);
508 g_return_val_if_fail(s2 != NULL, -1);
510 /* ensure strings are UTF-8 and lowercase */
511 tmp1 = utf8_strdown(s1);
512 if (! tmp1)
513 return 1;
514 tmp2 = utf8_strdown(s2);
515 if (! tmp2)
517 g_free(tmp1);
518 return -1;
521 /* compare */
522 result = strcmp(tmp1, tmp2);
524 g_free(tmp1);
525 g_free(tmp2);
526 return result;
531 * Truncates the input string to a given length.
532 * Characters are removed from the middle of the string, so the start and the end of string
533 * won't change.
535 * @param string Input string.
536 * @param truncate_length The length in characters of the resulting string.
538 * @return A copy of @a string which is truncated to @a truncate_length characters,
539 * should be freed when no longer needed.
541 * @since 0.17
543 /* This following function is taken from Gedit. */
544 GEANY_API_SYMBOL
545 gchar *utils_str_middle_truncate(const gchar *string, guint truncate_length)
547 GString *truncated;
548 guint length;
549 guint n_chars;
550 guint num_left_chars;
551 guint right_offset;
552 guint delimiter_length;
553 const gchar *delimiter = "\342\200\246";
555 g_return_val_if_fail(string != NULL, NULL);
557 length = strlen(string);
559 g_return_val_if_fail(g_utf8_validate(string, length, NULL), NULL);
561 /* It doesn't make sense to truncate strings to less than the size of the delimiter plus 2
562 * characters (one on each side) */
563 delimiter_length = g_utf8_strlen(delimiter, -1);
564 if (truncate_length < (delimiter_length + 2))
565 return g_strdup(string);
567 n_chars = g_utf8_strlen(string, length);
569 /* Make sure the string is not already small enough. */
570 if (n_chars <= truncate_length)
571 return g_strdup (string);
573 /* Find the 'middle' where the truncation will occur. */
574 num_left_chars = (truncate_length - delimiter_length) / 2;
575 right_offset = n_chars - truncate_length + num_left_chars + delimiter_length;
577 truncated = g_string_new_len(string, g_utf8_offset_to_pointer(string, num_left_chars) - string);
578 g_string_append(truncated, delimiter);
579 g_string_append(truncated, g_utf8_offset_to_pointer(string, right_offset));
581 return g_string_free(truncated, FALSE);
586 * @c NULL-safe string comparison. Returns @c TRUE if both @a a and @a b are @c NULL
587 * or if @a a and @a b refer to valid strings which are equal.
589 * @param a @nullable Pointer to first string or @c NULL.
590 * @param b @nullable Pointer to second string or @c NULL.
592 * @return @c TRUE if @a a equals @a b, else @c FALSE.
594 GEANY_API_SYMBOL
595 gboolean utils_str_equal(const gchar *a, const gchar *b)
597 /* (taken from libexo from os-cillation) */
598 if (a == NULL && b == NULL) return TRUE;
599 else if (a == NULL || b == NULL) return FALSE;
601 return strcmp(a, b) == 0;
606 * Removes the extension from @a filename and return the result in a newly allocated string.
608 * @param filename The filename to operate on.
610 * @return A newly-allocated string, should be freed when no longer needed.
612 GEANY_API_SYMBOL
613 gchar *utils_remove_ext_from_filename(const gchar *filename)
615 gchar *last_dot;
616 gchar *result;
617 gsize len;
619 g_return_val_if_fail(filename != NULL, NULL);
621 last_dot = strrchr(filename, '.');
622 if (! last_dot)
623 return g_strdup(filename);
625 len = (gsize) (last_dot - filename);
626 result = g_malloc(len + 1);
627 memcpy(result, filename, len);
628 result[len] = 0;
630 return result;
634 gchar utils_brace_opposite(gchar ch)
636 switch (ch)
638 case '(': return ')';
639 case ')': return '(';
640 case '[': return ']';
641 case ']': return '[';
642 case '{': return '}';
643 case '}': return '{';
644 case '<': return '>';
645 case '>': return '<';
646 default: return '\0';
651 /* Checks whether the given file can be written. locale_filename is expected in locale encoding.
652 * Returns 0 if it can be written, otherwise it returns errno */
653 gint utils_is_file_writable(const gchar *locale_filename)
655 gchar *file;
656 gint ret;
658 if (! g_file_test(locale_filename, G_FILE_TEST_EXISTS) &&
659 ! g_file_test(locale_filename, G_FILE_TEST_IS_DIR))
660 /* get the file's directory to check for write permission if it doesn't yet exist */
661 file = g_path_get_dirname(locale_filename);
662 else
663 file = g_strdup(locale_filename);
665 #ifdef G_OS_WIN32
666 /* use _waccess on Windows, access() doesn't accept special characters */
667 ret = win32_check_write_permission(file);
668 #else
670 /* access set also errno to "FILE NOT FOUND" even if locale_filename is writeable, so use
671 * errno only when access() explicitly returns an error */
672 if (access(file, R_OK | W_OK) != 0)
673 ret = errno;
674 else
675 ret = 0;
676 #endif
677 g_free(file);
678 return ret;
682 /* Replaces all occurrences of needle in haystack with replacement.
683 * Warning: *haystack must be a heap address; it may be freed and reassigned.
684 * Note: utils_string_replace_all() will always be faster when @a replacement is longer
685 * than @a needle.
686 * All strings have to be NULL-terminated.
687 * See utils_string_replace_all() for details. */
688 void utils_str_replace_all(gchar **haystack, const gchar *needle, const gchar *replacement)
690 GString *str;
692 g_return_if_fail(*haystack != NULL);
694 str = g_string_new(*haystack);
696 g_free(*haystack);
697 utils_string_replace_all(str, needle, replacement);
699 *haystack = g_string_free(str, FALSE);
703 gint utils_strpos(const gchar *haystack, const gchar *needle)
705 const gchar *sub;
707 if (! *needle)
708 return -1;
710 sub = strstr(haystack, needle);
711 if (! sub)
712 return -1;
714 return sub - haystack;
719 * Retrieves a formatted date/time string from strftime().
720 * This function should be preferred to directly calling strftime() since this function
721 * works on UTF-8 encoded strings.
723 * @param format The format string to pass to strftime(3). See the strftime(3)
724 * documentation for details, in UTF-8 encoding.
725 * @param time_to_use @nullable The date/time to use, in time_t format or @c NULL to use the current time.
727 * @return A newly-allocated string, should be freed when no longer needed.
729 * @since 0.16
731 GEANY_API_SYMBOL
732 gchar *utils_get_date_time(const gchar *format, time_t *time_to_use)
734 const struct tm *tm;
735 static gchar date[1024];
736 gchar *locale_format;
737 gsize len;
739 g_return_val_if_fail(format != NULL, NULL);
741 if (! g_utf8_validate(format, -1, NULL))
743 locale_format = g_locale_from_utf8(format, -1, NULL, NULL, NULL);
744 if (locale_format == NULL)
745 return NULL;
747 else
748 locale_format = g_strdup(format);
750 if (time_to_use != NULL)
751 tm = localtime(time_to_use);
752 else
754 time_t tp = time(NULL);
755 tm = localtime(&tp);
758 len = strftime(date, 1024, locale_format, tm);
759 g_free(locale_format);
760 if (len == 0)
761 return NULL;
763 if (! g_utf8_validate(date, len, NULL))
764 return g_locale_to_utf8(date, len, NULL, NULL, NULL);
765 else
766 return g_strdup(date);
770 gchar *utils_get_initials(const gchar *name)
772 gint i = 1, j = 1;
773 gchar *initials = g_malloc0(5);
775 initials[0] = name[0];
776 while (name[i] != '\0' && j < 4)
778 if (name[i] == ' ' && name[i + 1] != ' ')
780 initials[j++] = name[i + 1];
782 i++;
784 return initials;
789 * Wraps g_key_file_get_integer() to add a default value argument.
791 * @param config A GKeyFile object.
792 * @param section The group name to look in for the key.
793 * @param key The key to find.
794 * @param default_value The default value which will be returned when @a section or @a key
795 * don't exist.
797 * @return The value associated with @a key as an integer, or the given default value if the value
798 * could not be retrieved.
800 GEANY_API_SYMBOL
801 gint utils_get_setting_integer(GKeyFile *config, const gchar *section, const gchar *key,
802 const gint default_value)
804 gint tmp;
805 GError *error = NULL;
807 g_return_val_if_fail(config, default_value);
809 tmp = g_key_file_get_integer(config, section, key, &error);
810 if (error)
812 g_error_free(error);
813 return default_value;
815 return tmp;
820 * Wraps g_key_file_get_boolean() to add a default value argument.
822 * @param config A GKeyFile object.
823 * @param section The group name to look in for the key.
824 * @param key The key to find.
825 * @param default_value The default value which will be returned when @c section or @c key
826 * don't exist.
828 * @return The value associated with @a key as a boolean, or the given default value if the value
829 * could not be retrieved.
831 GEANY_API_SYMBOL
832 gboolean utils_get_setting_boolean(GKeyFile *config, const gchar *section, const gchar *key,
833 const gboolean default_value)
835 gboolean tmp;
836 GError *error = NULL;
838 g_return_val_if_fail(config, default_value);
840 tmp = g_key_file_get_boolean(config, section, key, &error);
841 if (error)
843 g_error_free(error);
844 return default_value;
846 return tmp;
851 * Wraps g_key_file_get_string() to add a default value argument.
853 * @param config A GKeyFile object.
854 * @param section The group name to look in for the key.
855 * @param key The key to find.
856 * @param default_value The default value which will be returned when @a section or @a key
857 * don't exist.
859 * @return A newly allocated string, either the value for @a key or a copy of the given
860 * default value if it could not be retrieved.
862 GEANY_API_SYMBOL
863 gchar *utils_get_setting_string(GKeyFile *config, const gchar *section, const gchar *key,
864 const gchar *default_value)
866 gchar *tmp;
868 g_return_val_if_fail(config, g_strdup(default_value));
870 tmp = g_key_file_get_string(config, section, key, NULL);
871 if (!tmp)
873 return g_strdup(default_value);
875 return tmp;
879 gchar *utils_get_hex_from_color(GdkColor *color)
881 g_return_val_if_fail(color != NULL, NULL);
883 return g_strdup_printf("#%02X%02X%02X",
884 (guint) (utils_scale_round(color->red / 256, 255)),
885 (guint) (utils_scale_round(color->green / 256, 255)),
886 (guint) (utils_scale_round(color->blue / 256, 255)));
890 /* Get directory from current file in the notebook.
891 * Returns dir string that should be freed or NULL, depending on whether current file is valid.
892 * Returned string is in UTF-8 encoding */
893 gchar *utils_get_current_file_dir_utf8(void)
895 GeanyDocument *doc = document_get_current();
897 if (doc != NULL)
899 /* get current filename */
900 const gchar *cur_fname = doc->file_name;
902 if (cur_fname != NULL)
904 /* get folder part from current filename */
905 return g_path_get_dirname(cur_fname); /* returns "." if no path */
909 return NULL; /* no file open */
913 /* very simple convenience function */
914 void utils_beep(void)
916 if (prefs.beep_on_errors)
917 gdk_beep();
921 /* taken from busybox, thanks */
922 gchar *utils_make_human_readable_str(guint64 size, gulong block_size,
923 gulong display_unit)
925 /* The code will adjust for additional (appended) units. */
926 static const gchar zero_and_units[] = { '0', 0, 'K', 'M', 'G', 'T' };
927 static const gchar fmt[] = "%Lu %c%c";
928 static const gchar fmt_tenths[] = "%Lu.%d %c%c";
930 guint64 val;
931 gint frac;
932 const gchar *u;
933 const gchar *f;
935 u = zero_and_units;
936 f = fmt;
937 frac = 0;
939 val = size * block_size;
940 if (val == 0)
941 return g_strdup(u);
943 if (display_unit)
945 val += display_unit/2; /* Deal with rounding. */
946 val /= display_unit; /* Don't combine with the line above!!! */
948 else
950 ++u;
951 while ((val >= 1024) && (u < zero_and_units + sizeof(zero_and_units) - 1))
953 f = fmt_tenths;
954 ++u;
955 frac = ((((gint)(val % 1024)) * 10) + (1024 / 2)) / 1024;
956 val /= 1024;
958 if (frac >= 10)
959 { /* We need to round up here. */
960 ++val;
961 frac = 0;
965 /* If f==fmt then 'frac' and 'u' are ignored. */
966 return g_strdup_printf(f, val, frac, *u, 'b');
970 /* converts a color representation using gdk_color_parse(), with additional
971 * support of the "0x" prefix as a synonym for "#" */
972 gboolean utils_parse_color(const gchar *spec, GdkColor *color)
974 gchar buf[64] = {0};
976 g_return_val_if_fail(spec != NULL, -1);
978 if (spec[0] == '0' && (spec[1] == 'x' || spec[1] == 'X'))
980 /* convert to # format for GDK to understand it */
981 buf[0] = '#';
982 strncpy(buf + 1, spec + 2, sizeof(buf) - 2);
983 spec = buf;
986 return gdk_color_parse(spec, color);
990 /* converts a GdkColor to the packed 24 bits BGR format, as understood by Scintilla
991 * returns a 24 bits BGR color, or -1 on failure */
992 gint utils_color_to_bgr(const GdkColor *c)
994 g_return_val_if_fail(c != NULL, -1);
995 return (c->red / 256) | ((c->green / 256) << 8) | ((c->blue / 256) << 16);
999 /* parses @p spec using utils_parse_color() and convert it to 24 bits BGR using
1000 * utils_color_to_bgr() */
1001 gint utils_parse_color_to_bgr(const gchar *spec)
1003 GdkColor color;
1004 if (utils_parse_color(spec, &color))
1005 return utils_color_to_bgr(&color);
1006 else
1007 return -1;
1011 /* Returns: newly allocated string with the current time formatted HH:MM:SS. */
1012 gchar *utils_get_current_time_string(void)
1014 const time_t tp = time(NULL);
1015 const struct tm *tmval = localtime(&tp);
1016 gchar *result = g_malloc0(9);
1018 strftime(result, 9, "%H:%M:%S", tmval);
1019 result[8] = '\0';
1020 return result;
1024 GIOChannel *utils_set_up_io_channel(
1025 gint fd, GIOCondition cond, gboolean nblock, GIOFunc func, gpointer data)
1027 GIOChannel *ioc;
1028 /*const gchar *encoding;*/
1030 #ifdef G_OS_WIN32
1031 ioc = g_io_channel_win32_new_fd(fd);
1032 #else
1033 ioc = g_io_channel_unix_new(fd);
1034 #endif
1036 if (nblock)
1037 g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
1039 g_io_channel_set_encoding(ioc, NULL, NULL);
1041 if (! g_get_charset(&encoding))
1042 { // hope this works reliably
1043 GError *error = NULL;
1044 g_io_channel_set_encoding(ioc, encoding, &error);
1045 if (error)
1047 geany_debug("%s: %s", G_STRFUNC, error->message);
1048 g_error_free(error);
1049 return ioc;
1053 /* "auto-close" ;-) */
1054 g_io_channel_set_close_on_unref(ioc, TRUE);
1056 g_io_add_watch(ioc, cond, func, data);
1057 g_io_channel_unref(ioc);
1059 return ioc;
1063 /* Contributed by Stefan Oltmanns, thanks.
1064 * Replaces \\, \r, \n, \t and \uXXX by their real counterparts.
1065 * keep_backslash is used for regex strings to leave '\\' and '\?' in place */
1066 gboolean utils_str_replace_escape(gchar *string, gboolean keep_backslash)
1068 gsize i, j, len;
1069 guint unicodechar;
1071 g_return_val_if_fail(string != NULL, FALSE);
1073 j = 0;
1074 len = strlen(string);
1075 for (i = 0; i < len; i++)
1077 if (string[i]=='\\')
1079 if (i++ >= strlen(string))
1081 return FALSE;
1083 switch (string[i])
1085 case '\\':
1086 if (keep_backslash)
1087 string[j++] = '\\';
1088 string[j] = '\\';
1089 break;
1090 case 'n':
1091 string[j] = '\n';
1092 break;
1093 case 'r':
1094 string[j] = '\r';
1095 break;
1096 case 't':
1097 string[j] = '\t';
1098 break;
1099 #if 0
1100 case 'x': /* Warning: May produce illegal utf-8 string! */
1101 i += 2;
1102 if (i >= strlen(string))
1104 return FALSE;
1106 if (isdigit(string[i - 1])) string[j] = string[i - 1] - 48;
1107 else if (isxdigit(string[i - 1])) string[j] = tolower(string[i - 1])-87;
1108 else return FALSE;
1109 string[j] <<= 4;
1110 if (isdigit(string[i])) string[j] |= string[i] - 48;
1111 else if (isxdigit(string[i])) string[j] |= tolower(string[i])-87;
1112 else return FALSE;
1113 break;
1114 #endif
1115 case 'u':
1117 i += 2;
1118 if (i >= strlen(string))
1120 return FALSE;
1122 if (isdigit(string[i - 1])) unicodechar = string[i - 1] - 48;
1123 else if (isxdigit(string[i - 1])) unicodechar = tolower(string[i - 1])-87;
1124 else return FALSE;
1125 unicodechar <<= 4;
1126 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1127 else if (isxdigit(string[i])) unicodechar |= tolower(string[i])-87;
1128 else return FALSE;
1129 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1130 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1132 i += 2;
1133 unicodechar <<= 8;
1134 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1135 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1136 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1137 else unicodechar |= tolower(string[i])-87;
1139 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1140 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1142 i += 2;
1143 unicodechar <<= 8;
1144 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1145 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1146 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1147 else unicodechar |= tolower(string[i])-87;
1149 if (unicodechar < 0x80)
1151 string[j] = unicodechar;
1153 else if (unicodechar < 0x800)
1155 string[j] = (unsigned char) ((unicodechar >> 6) | 0xC0);
1156 j++;
1157 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1159 else if (unicodechar < 0x10000)
1161 string[j] = (unsigned char) ((unicodechar >> 12) | 0xE0);
1162 j++;
1163 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1164 j++;
1165 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1167 else if (unicodechar < 0x110000) /* more chars are not allowed in unicode */
1169 string[j] = (unsigned char) ((unicodechar >> 18) | 0xF0);
1170 j++;
1171 string[j] = (unsigned char) (((unicodechar >> 12) & 0x3F) | 0x80);
1172 j++;
1173 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1174 j++;
1175 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1177 else
1179 return FALSE;
1181 break;
1183 default:
1184 /* unnecessary escapes are allowed */
1185 if (keep_backslash)
1186 string[j++] = '\\';
1187 string[j] = string[i];
1190 else
1192 string[j] = string[i];
1194 j++;
1196 while (j < i)
1198 string[j] = 0;
1199 j++;
1201 return TRUE;
1205 /* Wraps a string in place, replacing a space with a newline character.
1206 * wrapstart is the minimum position to start wrapping or -1 for default */
1207 gboolean utils_wrap_string(gchar *string, gint wrapstart)
1209 gchar *pos, *linestart;
1210 gboolean ret = FALSE;
1212 if (wrapstart < 0)
1213 wrapstart = 80;
1215 for (pos = linestart = string; *pos != '\0'; pos++)
1217 if (pos - linestart >= wrapstart && *pos == ' ')
1219 *pos = '\n';
1220 linestart = pos;
1221 ret = TRUE;
1224 return ret;
1229 * Converts the given UTF-8 encoded string into locale encoding.
1230 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1232 * @param utf8_text UTF-8 encoded text.
1234 * @return The converted string in locale encoding, or a copy of the input string if conversion
1235 * failed. Should be freed with g_free(). If @a utf8_text is @c NULL, @c NULL is returned.
1237 GEANY_API_SYMBOL
1238 gchar *utils_get_locale_from_utf8(const gchar *utf8_text)
1240 #ifdef G_OS_WIN32
1241 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1242 * which would result in wrongly converted strings */
1243 return g_strdup(utf8_text);
1244 #else
1245 gchar *locale_text;
1247 if (! utf8_text)
1248 return NULL;
1249 locale_text = g_locale_from_utf8(utf8_text, -1, NULL, NULL, NULL);
1250 if (locale_text == NULL)
1251 locale_text = g_strdup(utf8_text);
1252 return locale_text;
1253 #endif
1258 * Converts the given string (in locale encoding) into UTF-8 encoding.
1259 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1261 * @param locale_text Text in locale encoding.
1263 * @return The converted string in UTF-8 encoding, or a copy of the input string if conversion
1264 * failed. Should be freed with g_free(). If @a locale_text is @c NULL, @c NULL is returned.
1266 GEANY_API_SYMBOL
1267 gchar *utils_get_utf8_from_locale(const gchar *locale_text)
1269 #ifdef G_OS_WIN32
1270 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1271 * which would result in wrongly converted strings */
1272 return g_strdup(locale_text);
1273 #else
1274 gchar *utf8_text;
1276 if (! locale_text)
1277 return NULL;
1278 utf8_text = g_locale_to_utf8(locale_text, -1, NULL, NULL, NULL);
1279 if (utf8_text == NULL)
1280 utf8_text = g_strdup(locale_text);
1281 return utf8_text;
1282 #endif
1286 /* Pass pointers to free after arg_count.
1287 * The last argument must be NULL as an extra check that arg_count is correct. */
1288 void utils_free_pointers(gsize arg_count, ...)
1290 va_list a;
1291 gsize i;
1292 gpointer ptr;
1294 va_start(a, arg_count);
1295 for (i = 0; i < arg_count; i++)
1297 ptr = va_arg(a, gpointer);
1298 g_free(ptr);
1300 ptr = va_arg(a, gpointer);
1301 if (ptr)
1302 g_warning("Wrong arg_count!");
1303 va_end(a);
1307 /* currently unused */
1308 #if 0
1309 /* Creates a string array deep copy of a series of non-NULL strings.
1310 * The first argument is nothing special.
1311 * The list must be ended with NULL.
1312 * If first is NULL, NULL is returned. */
1313 gchar **utils_strv_new(const gchar *first, ...)
1315 gsize strvlen, i;
1316 va_list args;
1317 gchar *str;
1318 gchar **strv;
1320 g_return_val_if_fail(first != NULL, NULL);
1322 strvlen = 1; /* for first argument */
1324 /* count other arguments */
1325 va_start(args, first);
1326 for (; va_arg(args, gchar*) != NULL; strvlen++);
1327 va_end(args);
1329 strv = g_new(gchar*, strvlen + 1); /* +1 for NULL terminator */
1330 strv[0] = g_strdup(first);
1332 va_start(args, first);
1333 for (i = 1; str = va_arg(args, gchar*), str != NULL; i++)
1335 strv[i] = g_strdup(str);
1337 va_end(args);
1339 strv[i] = NULL;
1340 return strv;
1342 #endif
1346 * Creates a directory if it doesn't already exist.
1347 * Creates intermediate parent directories as needed, too.
1348 * The permissions of the created directory are set 0700.
1350 * @param path The path of the directory to create, in locale encoding.
1351 * @param create_parent_dirs Whether to create intermediate parent directories if necessary.
1353 * @return 0 if the directory was successfully created, otherwise the @c errno of the
1354 * failed operation is returned.
1356 GEANY_API_SYMBOL
1357 gint utils_mkdir(const gchar *path, gboolean create_parent_dirs)
1359 gint mode = 0700;
1360 gint result;
1362 if (path == NULL || strlen(path) == 0)
1363 return EFAULT;
1365 result = (create_parent_dirs) ? g_mkdir_with_parents(path, mode) : g_mkdir(path, mode);
1366 if (result != 0)
1367 return errno;
1368 return 0;
1373 * Gets a list of files from the specified directory.
1374 * Locale encoding is expected for @a path and used for the file list. The list and the data
1375 * in the list should be freed after use, e.g.:
1376 * @code
1377 * g_slist_foreach(list, (GFunc) g_free, NULL);
1378 * g_slist_free(list); @endcode
1380 * @note If you don't need a list you should use the foreach_dir() macro instead -
1381 * it's more efficient.
1383 * @param path The path of the directory to scan, in locale encoding.
1384 * @param full_path Whether to include the full path for each filename in the list. Obviously this
1385 * will use more memory.
1386 * @param sort Whether to sort alphabetically (UTF-8 safe).
1387 * @param error The location for storing a possible error, or @c NULL.
1389 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL if
1390 * no files were found. The list and its data should be freed when no longer needed.
1391 * @see utils_get_file_list().
1393 GEANY_API_SYMBOL
1394 GSList *utils_get_file_list_full(const gchar *path, gboolean full_path, gboolean sort, GError **error)
1396 GSList *list = NULL;
1397 GDir *dir;
1398 const gchar *filename;
1400 if (error)
1401 *error = NULL;
1402 g_return_val_if_fail(path != NULL, NULL);
1404 dir = g_dir_open(path, 0, error);
1405 if (dir == NULL)
1406 return NULL;
1408 foreach_dir(filename, dir)
1410 list = g_slist_prepend(list, full_path ?
1411 g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL) : g_strdup(filename));
1413 g_dir_close(dir);
1414 /* sorting last is quicker than on insertion */
1415 if (sort)
1416 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1417 return list;
1422 * Gets a sorted list of files from the specified directory.
1423 * Locale encoding is expected for @a path and used for the file list. The list and the data
1424 * in the list should be freed after use, e.g.:
1425 * @code
1426 * g_slist_foreach(list, (GFunc) g_free, NULL);
1427 * g_slist_free(list); @endcode
1429 * @param path The path of the directory to scan, in locale encoding.
1430 * @param length The location to store the number of non-@c NULL data items in the list,
1431 * unless @c NULL.
1432 * @param error The location for storing a possible error, or @c NULL.
1434 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL
1435 * if no files were found. The list and its data should be freed when no longer needed.
1436 * @see utils_get_file_list_full().
1438 GEANY_API_SYMBOL
1439 GSList *utils_get_file_list(const gchar *path, guint *length, GError **error)
1441 GSList *list = utils_get_file_list_full(path, FALSE, TRUE, error);
1443 if (length)
1444 *length = g_slist_length(list);
1445 return list;
1449 /* returns TRUE if any letter in str is a capital, FALSE otherwise. Should be Unicode safe. */
1450 gboolean utils_str_has_upper(const gchar *str)
1452 gunichar c;
1454 if (EMPTY(str) || ! g_utf8_validate(str, -1, NULL))
1455 return FALSE;
1457 while (*str != '\0')
1459 c = g_utf8_get_char(str);
1460 /* check only letters and stop once the first non-capital was found */
1461 if (g_unichar_isalpha(c) && g_unichar_isupper(c))
1462 return TRUE;
1463 /* FIXME don't write a const string */
1464 str = g_utf8_next_char(str);
1466 return FALSE;
1470 /* end can be -1 for haystack->len.
1471 * returns: position of found text or -1. */
1472 gint utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
1474 gint pos;
1476 g_return_val_if_fail(haystack != NULL, -1);
1477 if (haystack->len == 0)
1478 return -1;
1480 g_return_val_if_fail(start >= 0, -1);
1481 if (start >= (gint)haystack->len)
1482 return -1;
1484 g_return_val_if_fail(!EMPTY(needle), -1);
1486 if (end < 0)
1487 end = haystack->len;
1489 pos = utils_strpos(haystack->str + start, needle);
1490 if (pos == -1)
1491 return -1;
1493 pos += start;
1494 if (pos >= end)
1495 return -1;
1496 return pos;
1500 /* Replaces @len characters from offset @a pos.
1501 * len can be -1 to replace the remainder of @a str.
1502 * returns: pos + strlen(replace). */
1503 gint utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
1505 g_string_erase(str, pos, len);
1506 if (replace)
1508 g_string_insert(str, pos, replace);
1509 pos += strlen(replace);
1511 return pos;
1516 * Replaces all occurrences of @a needle in @a haystack with @a replace.
1517 * As of Geany 0.16, @a replace can match @a needle, so the following will work:
1518 * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
1520 * @param haystack The input string to operate on. This string is modified in place.
1521 * @param needle The string which should be replaced.
1522 * @param replace The replacement for @a needle.
1524 * @return Number of replacements made.
1526 GEANY_API_SYMBOL
1527 guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
1529 guint count = 0;
1530 gint pos = 0;
1531 gsize needle_length = strlen(needle);
1533 while (1)
1535 pos = utils_string_find(haystack, pos, -1, needle);
1537 if (pos == -1)
1538 break;
1540 pos = utils_string_replace(haystack, pos, needle_length, replace);
1541 count++;
1543 return count;
1548 * Replaces only the first occurrence of @a needle in @a haystack
1549 * with @a replace.
1550 * For details, see utils_string_replace_all().
1552 * @param haystack The input string to operate on. This string is modified in place.
1553 * @param needle The string which should be replaced.
1554 * @param replace The replacement for @a needle.
1556 * @return Number of replacements made.
1558 * @since 0.16
1560 GEANY_API_SYMBOL
1561 guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace)
1563 gint pos = utils_string_find(haystack, 0, -1, needle);
1565 if (pos == -1)
1566 return 0;
1568 utils_string_replace(haystack, pos, strlen(needle), replace);
1569 return 1;
1573 /* Similar to g_regex_replace but allows matching a subgroup.
1574 * match_num: which match to replace, 0 for whole match.
1575 * literal: FALSE to interpret escape sequences in @a replace.
1576 * returns: number of replacements.
1577 * bug: replaced text can affect matching of ^ or \b */
1578 guint utils_string_regex_replace_all(GString *haystack, GRegex *regex,
1579 guint match_num, const gchar *replace, gboolean literal)
1581 GMatchInfo *minfo;
1582 guint ret = 0;
1583 gint start = 0;
1585 g_assert(literal); /* escapes not implemented yet */
1586 g_return_val_if_fail(replace, 0);
1588 /* ensure haystack->str is not null */
1589 if (haystack->len == 0)
1590 return 0;
1592 /* passing a start position makes G_REGEX_MATCH_NOTBOL automatic */
1593 while (g_regex_match_full(regex, haystack->str, -1, start, 0, &minfo, NULL))
1595 gint end, len;
1597 g_match_info_fetch_pos(minfo, match_num, &start, &end);
1598 len = end - start;
1599 utils_string_replace(haystack, start, len, replace);
1600 ret++;
1602 /* skip past whole match */
1603 g_match_info_fetch_pos(minfo, 0, NULL, &end);
1604 start = end - len + strlen(replace);
1605 g_match_info_free(minfo);
1607 g_match_info_free(minfo);
1608 return ret;
1612 /* Get project or default startup directory (if set), or NULL. */
1613 const gchar *utils_get_default_dir_utf8(void)
1615 if (app->project && !EMPTY(app->project->base_path))
1617 return app->project->base_path;
1620 if (!EMPTY(prefs.default_open_path))
1622 return prefs.default_open_path;
1624 return NULL;
1629 * Wraps @c spawn_sync(), which see.
1631 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1632 * @param argv The child's argument vector.
1633 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1634 * @param flags Ignored.
1635 * @param child_setup @girskip Ignored.
1636 * @param user_data @girskip Ignored.
1637 * @param std_out @out @optional The return location for child output, or @c NULL.
1638 * @param std_err @out @optional The return location for child error messages, or @c NULL.
1639 * @param exit_status @out @optional The child exit status, as returned by waitpid(), or @c NULL.
1640 * @param error The return location for error or @c NULL.
1642 * @return @c TRUE on success, @c FALSE if an error was set.
1644 GEANY_API_SYMBOL
1645 gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1646 GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out,
1647 gchar **std_err, gint *exit_status, GError **error)
1649 GString *output = std_out ? g_string_new(NULL) : NULL;
1650 GString *errors = std_err ? g_string_new(NULL) : NULL;
1651 gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
1653 if (std_out)
1654 *std_out = g_string_free(output, !result);
1656 if (std_err)
1657 *std_err = g_string_free(errors, !result);
1659 return result;
1664 * Wraps @c spawn_async(), which see.
1666 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1667 * @param argv The child's argument vector.
1668 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1669 * @param flags Ignored.
1670 * @param child_setup @girskip Ignored.
1671 * @param user_data Ignored.
1672 * @param child_pid @out @nullable The return location for child process ID, or @c NULL.
1673 * @param error The return location for error or @c NULL.
1675 * @return @c TRUE on success, @c FALSE if an error was set.
1677 GEANY_API_SYMBOL
1678 gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1679 GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
1680 GError **error)
1682 return spawn_async(dir, NULL, argv, env, child_pid, error);
1686 /* Retrieves the path for the given URI.
1687 * It returns:
1688 * - the path which was determined by g_filename_from_uri() or GIO
1689 * - NULL if the URI is non-local and gvfs-fuse is not installed
1690 * - a new copy of 'uri' if it is not an URI. */
1691 gchar *utils_get_path_from_uri(const gchar *uri)
1693 gchar *locale_filename;
1695 g_return_val_if_fail(uri != NULL, NULL);
1697 if (! utils_is_uri(uri))
1698 return g_strdup(uri);
1700 /* this will work only for 'file://' URIs */
1701 locale_filename = g_filename_from_uri(uri, NULL, NULL);
1702 /* g_filename_from_uri() failed, so we probably have a non-local URI */
1703 if (locale_filename == NULL)
1705 GFile *file = g_file_new_for_uri(uri);
1706 locale_filename = g_file_get_path(file);
1707 g_object_unref(file);
1708 if (locale_filename == NULL)
1710 geany_debug("The URI '%s' could not be resolved to a local path. This means "
1711 "that the URI is invalid or that you don't have gvfs-fuse installed.", uri);
1715 return locale_filename;
1719 gboolean utils_is_uri(const gchar *uri)
1721 g_return_val_if_fail(uri != NULL, FALSE);
1723 return (strstr(uri, "://") != NULL);
1727 /* path should be in locale encoding */
1728 gboolean utils_is_remote_path(const gchar *path)
1730 g_return_val_if_fail(path != NULL, FALSE);
1732 /* if path is an URI and it doesn't start "file://", we take it as remote */
1733 if (utils_is_uri(path) && strncmp(path, "file:", 5) != 0)
1734 return TRUE;
1736 #ifndef G_OS_WIN32
1738 static gchar *fuse_path = NULL;
1739 static gsize len = 0;
1741 if (G_UNLIKELY(fuse_path == NULL))
1743 fuse_path = g_build_filename(g_get_home_dir(), ".gvfs", NULL);
1744 len = strlen(fuse_path);
1746 /* Comparing the file path against a hardcoded path is not the most elegant solution
1747 * but for now it is better than nothing. Ideally, g_file_new_for_path() should create
1748 * proper GFile objects for Fuse paths, but it only does in future GVFS
1749 * versions (gvfs 1.1.1). */
1750 return (strncmp(path, fuse_path, len) == 0);
1752 #endif
1754 return FALSE;
1758 /* Remove all relative and untidy elements from the path of @a filename.
1759 * @param filename must be a valid absolute path.
1760 * @see tm_get_real_path() - also resolves links. */
1761 void utils_tidy_path(gchar *filename)
1763 GString *str;
1764 const gchar *needle;
1765 gboolean preserve_double_backslash = FALSE;
1767 g_return_if_fail(g_path_is_absolute(filename));
1769 str = g_string_new(filename);
1771 if (str->len >= 2 && strncmp(str->str, "\\\\", 2) == 0)
1772 preserve_double_backslash = TRUE;
1774 #ifdef G_OS_WIN32
1775 /* using MSYS we can get Unix-style separators */
1776 utils_string_replace_all(str, "/", G_DIR_SEPARATOR_S);
1777 #endif
1778 /* replace "/./" and "//" */
1779 utils_string_replace_all(str, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1780 utils_string_replace_all(str, G_DIR_SEPARATOR_S G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1782 if (preserve_double_backslash)
1783 g_string_prepend(str, "\\");
1785 /* replace "/../" */
1786 needle = G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S;
1787 while (1)
1789 const gchar *c = strstr(str->str, needle);
1790 if (c == NULL)
1791 break;
1792 else
1794 gssize pos, sub_len;
1796 pos = c - str->str;
1797 if (pos <= 3)
1798 break; /* bad path */
1800 /* replace "/../" */
1801 g_string_erase(str, pos, strlen(needle));
1802 g_string_insert_c(str, pos, G_DIR_SEPARATOR);
1804 /* search for last "/" before found "/../" */
1805 c = g_strrstr_len(str->str, pos, G_DIR_SEPARATOR_S);
1806 sub_len = pos - (c - str->str);
1807 if (! c)
1808 break; /* bad path */
1810 pos = c - str->str; /* position of previous "/" */
1811 g_string_erase(str, pos, sub_len);
1814 if (str->len <= strlen(filename))
1815 memcpy(filename, str->str, str->len + 1);
1816 else
1817 g_warn_if_reached();
1818 g_string_free(str, TRUE);
1823 * Removes characters from a string, in place.
1825 * @param string String to search.
1826 * @param chars Characters to remove.
1828 * @return @a string - return value is only useful when nesting function calls, e.g.:
1829 * @code str = utils_str_remove_chars(g_strdup("f_o_o"), "_"); @endcode
1831 * @see @c g_strdelimit.
1833 GEANY_API_SYMBOL
1834 gchar *utils_str_remove_chars(gchar *string, const gchar *chars)
1836 const gchar *r;
1837 gchar *w = string;
1839 g_return_val_if_fail(string, NULL);
1840 if (G_UNLIKELY(EMPTY(chars)))
1841 return string;
1843 foreach_str(r, string)
1845 if (!strchr(chars, *r))
1846 *w++ = *r;
1848 *w = 0x0;
1849 return string;
1853 /* Gets list of sorted filenames with no path and no duplicates from user and system config */
1854 GSList *utils_get_config_files(const gchar *subdir)
1856 gchar *path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, subdir, NULL);
1857 GSList *list = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1858 GSList *syslist, *node;
1860 if (!list)
1862 utils_mkdir(path, FALSE);
1864 SETPTR(path, g_build_path(G_DIR_SEPARATOR_S, app->datadir, subdir, NULL));
1865 syslist = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1866 /* merge lists */
1867 list = g_slist_concat(list, syslist);
1869 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1870 /* remove duplicates (next to each other after sorting) */
1871 foreach_slist(node, list)
1873 if (node->next && utils_str_equal(node->next->data, node->data))
1875 GSList *old = node->next;
1877 g_free(old->data);
1878 node->next = old->next;
1879 g_slist_free1(old);
1882 g_free(path);
1883 return list;
1887 /* Suffix can be NULL or a string which should be appended to the Help URL like
1888 * an anchor link, e.g. "#some_anchor". */
1889 gchar *utils_get_help_url(const gchar *suffix)
1891 gint skip;
1892 gchar *uri;
1894 #ifdef G_OS_WIN32
1895 skip = 8;
1896 uri = g_strconcat("file:///", app->docdir, "/index.html", NULL);
1897 g_strdelimit(uri, "\\", '/'); /* replace '\\' by '/' */
1898 #else
1899 skip = 7;
1900 uri = g_strconcat("file://", app->docdir, "/index.html", NULL);
1901 #endif
1903 if (! g_file_test(uri + skip, G_FILE_TEST_IS_REGULAR))
1904 { /* fall back to online documentation if it is not found on the hard disk */
1905 g_free(uri);
1906 uri = g_strconcat(GEANY_HOMEPAGE, "manual/", VERSION, "/index.html", NULL);
1909 if (suffix != NULL)
1911 SETPTR(uri, g_strconcat(uri, suffix, NULL));
1914 return uri;
1918 static gboolean str_in_array(const gchar **haystack, const gchar *needle)
1920 const gchar **p;
1922 for (p = haystack; *p != NULL; ++p)
1924 if (utils_str_equal(*p, needle))
1925 return TRUE;
1927 return FALSE;
1932 * Copies the current environment into a new array.
1933 * @a exclude_vars is a @c NULL-terminated array of variable names which should be not copied.
1934 * All further arguments are key, value pairs of variables which should be added to
1935 * the environment.
1937 * The argument list must be @c NULL-terminated.
1939 * @param exclude_vars @c NULL-terminated array of variable names to exclude.
1940 * @param first_varname Name of the first variable to copy into the new array.
1941 * @param ... Key-value pairs of variable names and values, @c NULL-terminated.
1943 * @return @transfer{full} The new environment array. Use @c g_strfreev() to free it.
1945 GEANY_API_SYMBOL
1946 gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...)
1948 gchar **result;
1949 gchar **p;
1950 gchar **env;
1951 va_list args;
1952 const gchar *key, *value;
1953 guint n, o;
1955 /* count the additional variables */
1956 va_start(args, first_varname);
1957 for (o = 1; va_arg(args, gchar*) != NULL; o++);
1958 va_end(args);
1959 /* the passed arguments should be even (key, value pairs) */
1960 g_return_val_if_fail(o % 2 == 0, NULL);
1962 o /= 2;
1964 /* get all the environ variables */
1965 env = g_listenv();
1967 /* create an array large enough to hold the new environment */
1968 n = g_strv_length(env);
1969 /* 'n + o + 1' could leak a little bit when exclude_vars is set */
1970 result = g_new(gchar *, n + o + 1);
1972 /* copy the environment */
1973 for (n = 0, p = env; *p != NULL; ++p)
1975 /* copy the variable */
1976 value = g_getenv(*p);
1977 if (G_LIKELY(value != NULL))
1979 /* skip excluded variables */
1980 if (exclude_vars != NULL && str_in_array(exclude_vars, *p))
1981 continue;
1983 result[n++] = g_strconcat(*p, "=", value, NULL);
1986 g_strfreev(env);
1988 /* now add additional variables */
1989 va_start(args, first_varname);
1990 key = first_varname;
1991 value = va_arg(args, gchar*);
1992 while (key != NULL)
1994 result[n++] = g_strconcat(key, "=", value, NULL);
1996 key = va_arg(args, gchar*);
1997 if (key == NULL)
1998 break;
1999 value = va_arg(args, gchar*);
2001 va_end(args);
2003 result[n] = NULL;
2005 return result;
2009 /* Joins @a first and @a second into a new string vector, freeing the originals.
2010 * The original contents are reused. */
2011 gchar **utils_strv_join(gchar **first, gchar **second)
2013 gchar **strv;
2014 gchar **rptr, **wptr;
2016 if (!first)
2017 return second;
2018 if (!second)
2019 return first;
2021 strv = g_new0(gchar*, g_strv_length(first) + g_strv_length(second) + 1);
2022 wptr = strv;
2024 foreach_strv(rptr, first)
2025 *wptr++ = *rptr;
2026 foreach_strv(rptr, second)
2027 *wptr++ = *rptr;
2029 g_free(first);
2030 g_free(second);
2031 return strv;
2035 /* Try to parse a date using g_date_set_parse(). It doesn't take any format hint,
2036 * obviously g_date_set_parse() uses some magic.
2037 * The returned GDate object must be freed. */
2038 GDate *utils_parse_date(const gchar *input)
2040 GDate *date = g_date_new();
2042 g_date_set_parse(date, input);
2044 if (g_date_valid(date))
2045 return date;
2047 g_date_free(date);
2048 return NULL;
2052 gchar *utils_parse_and_format_build_date(const gchar *input)
2054 gchar date_buf[255];
2055 GDate *date = utils_parse_date(input);
2057 if (date != NULL)
2059 g_date_strftime(date_buf, sizeof(date_buf), GEANY_TEMPLATES_FORMAT_DATE, date);
2060 g_date_free(date);
2061 return g_strdup(date_buf);
2064 return g_strdup(input);
2068 gchar *utils_get_user_config_dir(void)
2070 #ifdef G_OS_WIN32
2071 return win32_get_user_config_dir();
2072 #else
2073 return g_build_filename(g_get_user_config_dir(), "geany", NULL);
2074 #endif
2078 static gboolean is_osx_bundle(void)
2080 #ifdef MAC_INTEGRATION
2081 gchar *bundle_id = gtkosx_application_get_bundle_id();
2082 if (bundle_id)
2084 g_free(bundle_id);
2085 return TRUE;
2087 #endif
2088 return FALSE;
2092 const gchar *utils_resource_dir(GeanyResourceDirType type)
2094 static const gchar *resdirs[RESOURCE_DIR_COUNT] = {NULL};
2096 if (!resdirs[RESOURCE_DIR_DATA])
2098 #ifdef G_OS_WIN32
2099 gchar *prefix = win32_get_installation_dir();
2101 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "data", NULL);
2102 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2103 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2104 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2105 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2106 g_free(prefix);
2107 #else
2108 if (is_osx_bundle())
2110 # ifdef MAC_INTEGRATION
2111 gchar *prefix = gtkosx_application_get_resource_path();
2113 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "share", "geany", NULL);
2114 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2115 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2116 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2117 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2118 g_free(prefix);
2119 # endif
2121 else
2123 resdirs[RESOURCE_DIR_DATA] = g_build_filename(GEANY_DATADIR, "geany", NULL);
2124 resdirs[RESOURCE_DIR_ICON] = g_build_filename(GEANY_DATADIR, "icons", NULL);
2125 resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL);
2126 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL);
2127 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL);
2129 #endif
2132 return resdirs[type];
2136 void utils_start_new_geany_instance(const gchar *doc_path)
2138 const gchar *command = is_osx_bundle() ? "open" : "geany";
2139 gchar *exec_path = g_find_program_in_path(command);
2141 if (exec_path)
2143 GError *err = NULL;
2144 const gchar *argv[6]; // max args + 1
2145 gint argc = 0;
2147 argv[argc++] = exec_path;
2148 if (is_osx_bundle())
2150 argv[argc++] = "-n";
2151 argv[argc++] = "-a";
2152 argv[argc++] = "Geany";
2153 argv[argc++] = doc_path;
2155 else
2157 argv[argc++] = "-i";
2158 argv[argc++] = doc_path;
2160 argv[argc] = NULL;
2162 if (!utils_spawn_async(NULL, (gchar**) argv, NULL, 0, NULL, NULL, NULL, &err))
2164 g_printerr("Unable to open new window: %s", err->message);
2165 g_error_free(err);
2167 g_free(exec_path);
2169 else
2170 g_printerr("Unable to find 'geany'");