ruby: Report singleton type inside anonymous classes at a class level
[geany-mirror.git] / src / utils.c
blob7de13071fc683f3510c0278ce472d27774c9fa7e
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 gchar **utils_read_file_in_array(const gchar *filename)
1065 gchar **result = NULL;
1066 gchar *data;
1068 g_return_val_if_fail(filename != NULL, NULL);
1070 g_file_get_contents(filename, &data, NULL, NULL);
1072 if (data != NULL)
1074 result = g_strsplit_set(data, "\r\n", -1);
1075 g_free(data);
1078 return result;
1082 /* Contributed by Stefan Oltmanns, thanks.
1083 * Replaces \\, \r, \n, \t and \uXXX by their real counterparts.
1084 * keep_backslash is used for regex strings to leave '\\' and '\?' in place */
1085 gboolean utils_str_replace_escape(gchar *string, gboolean keep_backslash)
1087 gsize i, j, len;
1088 guint unicodechar;
1090 g_return_val_if_fail(string != NULL, FALSE);
1092 j = 0;
1093 len = strlen(string);
1094 for (i = 0; i < len; i++)
1096 if (string[i]=='\\')
1098 if (i++ >= strlen(string))
1100 return FALSE;
1102 switch (string[i])
1104 case '\\':
1105 if (keep_backslash)
1106 string[j++] = '\\';
1107 string[j] = '\\';
1108 break;
1109 case 'n':
1110 string[j] = '\n';
1111 break;
1112 case 'r':
1113 string[j] = '\r';
1114 break;
1115 case 't':
1116 string[j] = '\t';
1117 break;
1118 #if 0
1119 case 'x': /* Warning: May produce illegal utf-8 string! */
1120 i += 2;
1121 if (i >= strlen(string))
1123 return FALSE;
1125 if (isdigit(string[i - 1])) string[j] = string[i - 1] - 48;
1126 else if (isxdigit(string[i - 1])) string[j] = tolower(string[i - 1])-87;
1127 else return FALSE;
1128 string[j] <<= 4;
1129 if (isdigit(string[i])) string[j] |= string[i] - 48;
1130 else if (isxdigit(string[i])) string[j] |= tolower(string[i])-87;
1131 else return FALSE;
1132 break;
1133 #endif
1134 case 'u':
1136 i += 2;
1137 if (i >= strlen(string))
1139 return FALSE;
1141 if (isdigit(string[i - 1])) unicodechar = string[i - 1] - 48;
1142 else if (isxdigit(string[i - 1])) unicodechar = tolower(string[i - 1])-87;
1143 else return FALSE;
1144 unicodechar <<= 4;
1145 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1146 else if (isxdigit(string[i])) unicodechar |= tolower(string[i])-87;
1147 else return FALSE;
1148 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1149 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1151 i += 2;
1152 unicodechar <<= 8;
1153 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1154 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1155 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1156 else unicodechar |= tolower(string[i])-87;
1158 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1159 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1161 i += 2;
1162 unicodechar <<= 8;
1163 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1164 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1165 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1166 else unicodechar |= tolower(string[i])-87;
1168 if (unicodechar < 0x80)
1170 string[j] = unicodechar;
1172 else if (unicodechar < 0x800)
1174 string[j] = (unsigned char) ((unicodechar >> 6) | 0xC0);
1175 j++;
1176 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1178 else if (unicodechar < 0x10000)
1180 string[j] = (unsigned char) ((unicodechar >> 12) | 0xE0);
1181 j++;
1182 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1183 j++;
1184 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1186 else if (unicodechar < 0x110000) /* more chars are not allowed in unicode */
1188 string[j] = (unsigned char) ((unicodechar >> 18) | 0xF0);
1189 j++;
1190 string[j] = (unsigned char) (((unicodechar >> 12) & 0x3F) | 0x80);
1191 j++;
1192 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1193 j++;
1194 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1196 else
1198 return FALSE;
1200 break;
1202 default:
1203 /* unnecessary escapes are allowed */
1204 if (keep_backslash)
1205 string[j++] = '\\';
1206 string[j] = string[i];
1209 else
1211 string[j] = string[i];
1213 j++;
1215 while (j < i)
1217 string[j] = 0;
1218 j++;
1220 return TRUE;
1224 /* Wraps a string in place, replacing a space with a newline character.
1225 * wrapstart is the minimum position to start wrapping or -1 for default */
1226 gboolean utils_wrap_string(gchar *string, gint wrapstart)
1228 gchar *pos, *linestart;
1229 gboolean ret = FALSE;
1231 if (wrapstart < 0)
1232 wrapstart = 80;
1234 for (pos = linestart = string; *pos != '\0'; pos++)
1236 if (pos - linestart >= wrapstart && *pos == ' ')
1238 *pos = '\n';
1239 linestart = pos;
1240 ret = TRUE;
1243 return ret;
1248 * Converts the given UTF-8 encoded string into locale encoding.
1249 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1251 * @param utf8_text UTF-8 encoded text.
1253 * @return The converted string in locale encoding, or a copy of the input string if conversion
1254 * failed. Should be freed with g_free(). If @a utf8_text is @c NULL, @c NULL is returned.
1256 GEANY_API_SYMBOL
1257 gchar *utils_get_locale_from_utf8(const gchar *utf8_text)
1259 #ifdef G_OS_WIN32
1260 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1261 * which would result in wrongly converted strings */
1262 return g_strdup(utf8_text);
1263 #else
1264 gchar *locale_text;
1266 if (! utf8_text)
1267 return NULL;
1268 locale_text = g_locale_from_utf8(utf8_text, -1, NULL, NULL, NULL);
1269 if (locale_text == NULL)
1270 locale_text = g_strdup(utf8_text);
1271 return locale_text;
1272 #endif
1277 * Converts the given string (in locale encoding) into UTF-8 encoding.
1278 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1280 * @param locale_text Text in locale encoding.
1282 * @return The converted string in UTF-8 encoding, or a copy of the input string if conversion
1283 * failed. Should be freed with g_free(). If @a locale_text is @c NULL, @c NULL is returned.
1285 GEANY_API_SYMBOL
1286 gchar *utils_get_utf8_from_locale(const gchar *locale_text)
1288 #ifdef G_OS_WIN32
1289 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1290 * which would result in wrongly converted strings */
1291 return g_strdup(locale_text);
1292 #else
1293 gchar *utf8_text;
1295 if (! locale_text)
1296 return NULL;
1297 utf8_text = g_locale_to_utf8(locale_text, -1, NULL, NULL, NULL);
1298 if (utf8_text == NULL)
1299 utf8_text = g_strdup(locale_text);
1300 return utf8_text;
1301 #endif
1305 /* Pass pointers to free after arg_count.
1306 * The last argument must be NULL as an extra check that arg_count is correct. */
1307 void utils_free_pointers(gsize arg_count, ...)
1309 va_list a;
1310 gsize i;
1311 gpointer ptr;
1313 va_start(a, arg_count);
1314 for (i = 0; i < arg_count; i++)
1316 ptr = va_arg(a, gpointer);
1317 g_free(ptr);
1319 ptr = va_arg(a, gpointer);
1320 if (ptr)
1321 g_warning("Wrong arg_count!");
1322 va_end(a);
1326 /* currently unused */
1327 #if 0
1328 /* Creates a string array deep copy of a series of non-NULL strings.
1329 * The first argument is nothing special.
1330 * The list must be ended with NULL.
1331 * If first is NULL, NULL is returned. */
1332 gchar **utils_strv_new(const gchar *first, ...)
1334 gsize strvlen, i;
1335 va_list args;
1336 gchar *str;
1337 gchar **strv;
1339 g_return_val_if_fail(first != NULL, NULL);
1341 strvlen = 1; /* for first argument */
1343 /* count other arguments */
1344 va_start(args, first);
1345 for (; va_arg(args, gchar*) != NULL; strvlen++);
1346 va_end(args);
1348 strv = g_new(gchar*, strvlen + 1); /* +1 for NULL terminator */
1349 strv[0] = g_strdup(first);
1351 va_start(args, first);
1352 for (i = 1; str = va_arg(args, gchar*), str != NULL; i++)
1354 strv[i] = g_strdup(str);
1356 va_end(args);
1358 strv[i] = NULL;
1359 return strv;
1361 #endif
1365 * Creates a directory if it doesn't already exist.
1366 * Creates intermediate parent directories as needed, too.
1367 * The permissions of the created directory are set 0700.
1369 * @param path The path of the directory to create, in locale encoding.
1370 * @param create_parent_dirs Whether to create intermediate parent directories if necessary.
1372 * @return 0 if the directory was successfully created, otherwise the @c errno of the
1373 * failed operation is returned.
1375 GEANY_API_SYMBOL
1376 gint utils_mkdir(const gchar *path, gboolean create_parent_dirs)
1378 gint mode = 0700;
1379 gint result;
1381 if (path == NULL || strlen(path) == 0)
1382 return EFAULT;
1384 result = (create_parent_dirs) ? g_mkdir_with_parents(path, mode) : g_mkdir(path, mode);
1385 if (result != 0)
1386 return errno;
1387 return 0;
1392 * Gets a list of files from the specified directory.
1393 * Locale encoding is expected for @a path and used for the file list. The list and the data
1394 * in the list should be freed after use, e.g.:
1395 * @code
1396 * g_slist_foreach(list, (GFunc) g_free, NULL);
1397 * g_slist_free(list); @endcode
1399 * @note If you don't need a list you should use the foreach_dir() macro instead -
1400 * it's more efficient.
1402 * @param path The path of the directory to scan, in locale encoding.
1403 * @param full_path Whether to include the full path for each filename in the list. Obviously this
1404 * will use more memory.
1405 * @param sort Whether to sort alphabetically (UTF-8 safe).
1406 * @param error The location for storing a possible error, or @c NULL.
1408 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL if
1409 * no files were found. The list and its data should be freed when no longer needed.
1410 * @see utils_get_file_list().
1412 GEANY_API_SYMBOL
1413 GSList *utils_get_file_list_full(const gchar *path, gboolean full_path, gboolean sort, GError **error)
1415 GSList *list = NULL;
1416 GDir *dir;
1417 const gchar *filename;
1419 if (error)
1420 *error = NULL;
1421 g_return_val_if_fail(path != NULL, NULL);
1423 dir = g_dir_open(path, 0, error);
1424 if (dir == NULL)
1425 return NULL;
1427 foreach_dir(filename, dir)
1429 list = g_slist_prepend(list, full_path ?
1430 g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL) : g_strdup(filename));
1432 g_dir_close(dir);
1433 /* sorting last is quicker than on insertion */
1434 if (sort)
1435 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1436 return list;
1441 * Gets a sorted list of files from the specified directory.
1442 * Locale encoding is expected for @a path and used for the file list. The list and the data
1443 * in the list should be freed after use, e.g.:
1444 * @code
1445 * g_slist_foreach(list, (GFunc) g_free, NULL);
1446 * g_slist_free(list); @endcode
1448 * @param path The path of the directory to scan, in locale encoding.
1449 * @param length The location to store the number of non-@c NULL data items in the list,
1450 * unless @c NULL.
1451 * @param error The location for storing a possible error, or @c NULL.
1453 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL
1454 * if no files were found. The list and its data should be freed when no longer needed.
1455 * @see utils_get_file_list_full().
1457 GEANY_API_SYMBOL
1458 GSList *utils_get_file_list(const gchar *path, guint *length, GError **error)
1460 GSList *list = utils_get_file_list_full(path, FALSE, TRUE, error);
1462 if (length)
1463 *length = g_slist_length(list);
1464 return list;
1468 /* returns TRUE if any letter in str is a capital, FALSE otherwise. Should be Unicode safe. */
1469 gboolean utils_str_has_upper(const gchar *str)
1471 gunichar c;
1473 if (EMPTY(str) || ! g_utf8_validate(str, -1, NULL))
1474 return FALSE;
1476 while (*str != '\0')
1478 c = g_utf8_get_char(str);
1479 /* check only letters and stop once the first non-capital was found */
1480 if (g_unichar_isalpha(c) && g_unichar_isupper(c))
1481 return TRUE;
1482 /* FIXME don't write a const string */
1483 str = g_utf8_next_char(str);
1485 return FALSE;
1489 /* end can be -1 for haystack->len.
1490 * returns: position of found text or -1. */
1491 gint utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
1493 gint pos;
1495 g_return_val_if_fail(haystack != NULL, -1);
1496 if (haystack->len == 0)
1497 return -1;
1499 g_return_val_if_fail(start >= 0, -1);
1500 if (start >= (gint)haystack->len)
1501 return -1;
1503 g_return_val_if_fail(!EMPTY(needle), -1);
1505 if (end < 0)
1506 end = haystack->len;
1508 pos = utils_strpos(haystack->str + start, needle);
1509 if (pos == -1)
1510 return -1;
1512 pos += start;
1513 if (pos >= end)
1514 return -1;
1515 return pos;
1519 /* Replaces @len characters from offset @a pos.
1520 * len can be -1 to replace the remainder of @a str.
1521 * returns: pos + strlen(replace). */
1522 gint utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
1524 g_string_erase(str, pos, len);
1525 if (replace)
1527 g_string_insert(str, pos, replace);
1528 pos += strlen(replace);
1530 return pos;
1535 * Replaces all occurrences of @a needle in @a haystack with @a replace.
1536 * As of Geany 0.16, @a replace can match @a needle, so the following will work:
1537 * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
1539 * @param haystack The input string to operate on. This string is modified in place.
1540 * @param needle The string which should be replaced.
1541 * @param replace The replacement for @a needle.
1543 * @return Number of replacements made.
1545 GEANY_API_SYMBOL
1546 guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
1548 guint count = 0;
1549 gint pos = 0;
1550 gsize needle_length = strlen(needle);
1552 while (1)
1554 pos = utils_string_find(haystack, pos, -1, needle);
1556 if (pos == -1)
1557 break;
1559 pos = utils_string_replace(haystack, pos, needle_length, replace);
1560 count++;
1562 return count;
1567 * Replaces only the first occurrence of @a needle in @a haystack
1568 * with @a replace.
1569 * For details, see utils_string_replace_all().
1571 * @param haystack The input string to operate on. This string is modified in place.
1572 * @param needle The string which should be replaced.
1573 * @param replace The replacement for @a needle.
1575 * @return Number of replacements made.
1577 * @since 0.16
1579 GEANY_API_SYMBOL
1580 guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace)
1582 gint pos = utils_string_find(haystack, 0, -1, needle);
1584 if (pos == -1)
1585 return 0;
1587 utils_string_replace(haystack, pos, strlen(needle), replace);
1588 return 1;
1592 /* Similar to g_regex_replace but allows matching a subgroup.
1593 * match_num: which match to replace, 0 for whole match.
1594 * literal: FALSE to interpret escape sequences in @a replace.
1595 * returns: number of replacements.
1596 * bug: replaced text can affect matching of ^ or \b */
1597 guint utils_string_regex_replace_all(GString *haystack, GRegex *regex,
1598 guint match_num, const gchar *replace, gboolean literal)
1600 GMatchInfo *minfo;
1601 guint ret = 0;
1602 gint start = 0;
1604 g_assert(literal); /* escapes not implemented yet */
1605 g_return_val_if_fail(replace, 0);
1607 /* ensure haystack->str is not null */
1608 if (haystack->len == 0)
1609 return 0;
1611 /* passing a start position makes G_REGEX_MATCH_NOTBOL automatic */
1612 while (g_regex_match_full(regex, haystack->str, -1, start, 0, &minfo, NULL))
1614 gint end, len;
1616 g_match_info_fetch_pos(minfo, match_num, &start, &end);
1617 len = end - start;
1618 utils_string_replace(haystack, start, len, replace);
1619 ret++;
1621 /* skip past whole match */
1622 g_match_info_fetch_pos(minfo, 0, NULL, &end);
1623 start = end - len + strlen(replace);
1624 g_match_info_free(minfo);
1626 g_match_info_free(minfo);
1627 return ret;
1631 /* Get project or default startup directory (if set), or NULL. */
1632 const gchar *utils_get_default_dir_utf8(void)
1634 if (app->project && !EMPTY(app->project->base_path))
1636 return app->project->base_path;
1639 if (!EMPTY(prefs.default_open_path))
1641 return prefs.default_open_path;
1643 return NULL;
1648 * Wraps @c spawn_sync(), which see.
1650 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1651 * @param argv The child's argument vector.
1652 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1653 * @param flags Ignored.
1654 * @param child_setup @girskip Ignored.
1655 * @param user_data @girskip Ignored.
1656 * @param std_out @out @optional The return location for child output, or @c NULL.
1657 * @param std_err @out @optional The return location for child error messages, or @c NULL.
1658 * @param exit_status @out @optional The child exit status, as returned by waitpid(), or @c NULL.
1659 * @param error The return location for error or @c NULL.
1661 * @return @c TRUE on success, @c FALSE if an error was set.
1663 GEANY_API_SYMBOL
1664 gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1665 GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out,
1666 gchar **std_err, gint *exit_status, GError **error)
1668 GString *output = std_out ? g_string_new(NULL) : NULL;
1669 GString *errors = std_err ? g_string_new(NULL) : NULL;
1670 gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
1672 if (std_out)
1673 *std_out = g_string_free(output, !result);
1675 if (std_err)
1676 *std_err = g_string_free(errors, !result);
1678 return result;
1683 * Wraps @c spawn_async(), which see.
1685 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1686 * @param argv The child's argument vector.
1687 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1688 * @param flags Ignored.
1689 * @param child_setup @girskip Ignored.
1690 * @param user_data Ignored.
1691 * @param child_pid @nullable The return location for child process ID, or @c NULL.
1692 * @param error The return location for error or @c NULL.
1694 * @return @c TRUE on success, @c FALSE if an error was set.
1696 GEANY_API_SYMBOL
1697 gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1698 GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
1699 GError **error)
1701 return spawn_async(dir, NULL, argv, env, child_pid, error);
1705 /* Retrieves the path for the given URI.
1706 * It returns:
1707 * - the path which was determined by g_filename_from_uri() or GIO
1708 * - NULL if the URI is non-local and gvfs-fuse is not installed
1709 * - a new copy of 'uri' if it is not an URI. */
1710 gchar *utils_get_path_from_uri(const gchar *uri)
1712 gchar *locale_filename;
1714 g_return_val_if_fail(uri != NULL, NULL);
1716 if (! utils_is_uri(uri))
1717 return g_strdup(uri);
1719 /* this will work only for 'file://' URIs */
1720 locale_filename = g_filename_from_uri(uri, NULL, NULL);
1721 /* g_filename_from_uri() failed, so we probably have a non-local URI */
1722 if (locale_filename == NULL)
1724 GFile *file = g_file_new_for_uri(uri);
1725 locale_filename = g_file_get_path(file);
1726 g_object_unref(file);
1727 if (locale_filename == NULL)
1729 geany_debug("The URI '%s' could not be resolved to a local path. This means "
1730 "that the URI is invalid or that you don't have gvfs-fuse installed.", uri);
1734 return locale_filename;
1738 gboolean utils_is_uri(const gchar *uri)
1740 g_return_val_if_fail(uri != NULL, FALSE);
1742 return (strstr(uri, "://") != NULL);
1746 /* path should be in locale encoding */
1747 gboolean utils_is_remote_path(const gchar *path)
1749 g_return_val_if_fail(path != NULL, FALSE);
1751 /* if path is an URI and it doesn't start "file://", we take it as remote */
1752 if (utils_is_uri(path) && strncmp(path, "file:", 5) != 0)
1753 return TRUE;
1755 #ifndef G_OS_WIN32
1757 static gchar *fuse_path = NULL;
1758 static gsize len = 0;
1760 if (G_UNLIKELY(fuse_path == NULL))
1762 fuse_path = g_build_filename(g_get_home_dir(), ".gvfs", NULL);
1763 len = strlen(fuse_path);
1765 /* Comparing the file path against a hardcoded path is not the most elegant solution
1766 * but for now it is better than nothing. Ideally, g_file_new_for_path() should create
1767 * proper GFile objects for Fuse paths, but it only does in future GVFS
1768 * versions (gvfs 1.1.1). */
1769 return (strncmp(path, fuse_path, len) == 0);
1771 #endif
1773 return FALSE;
1777 /* Remove all relative and untidy elements from the path of @a filename.
1778 * @param filename must be a valid absolute path.
1779 * @see tm_get_real_path() - also resolves links. */
1780 void utils_tidy_path(gchar *filename)
1782 GString *str;
1783 const gchar *needle;
1784 gboolean preserve_double_backslash = FALSE;
1786 g_return_if_fail(g_path_is_absolute(filename));
1788 str = g_string_new(filename);
1790 if (str->len >= 2 && strncmp(str->str, "\\\\", 2) == 0)
1791 preserve_double_backslash = TRUE;
1793 #ifdef G_OS_WIN32
1794 /* using MSYS we can get Unix-style separators */
1795 utils_string_replace_all(str, "/", G_DIR_SEPARATOR_S);
1796 #endif
1797 /* replace "/./" and "//" */
1798 utils_string_replace_all(str, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1799 utils_string_replace_all(str, G_DIR_SEPARATOR_S G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1801 if (preserve_double_backslash)
1802 g_string_prepend(str, "\\");
1804 /* replace "/../" */
1805 needle = G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S;
1806 while (1)
1808 const gchar *c = strstr(str->str, needle);
1809 if (c == NULL)
1810 break;
1811 else
1813 gssize pos, sub_len;
1815 pos = c - str->str;
1816 if (pos <= 3)
1817 break; /* bad path */
1819 /* replace "/../" */
1820 g_string_erase(str, pos, strlen(needle));
1821 g_string_insert_c(str, pos, G_DIR_SEPARATOR);
1823 /* search for last "/" before found "/../" */
1824 c = g_strrstr_len(str->str, pos, G_DIR_SEPARATOR_S);
1825 sub_len = pos - (c - str->str);
1826 if (! c)
1827 break; /* bad path */
1829 pos = c - str->str; /* position of previous "/" */
1830 g_string_erase(str, pos, sub_len);
1833 if (str->len <= strlen(filename))
1834 memcpy(filename, str->str, str->len + 1);
1835 else
1836 g_warn_if_reached();
1837 g_string_free(str, TRUE);
1842 * Removes characters from a string, in place.
1844 * @param string String to search.
1845 * @param chars Characters to remove.
1847 * @return @a string - return value is only useful when nesting function calls, e.g.:
1848 * @code str = utils_str_remove_chars(g_strdup("f_o_o"), "_"); @endcode
1850 * @see @c g_strdelimit.
1852 GEANY_API_SYMBOL
1853 gchar *utils_str_remove_chars(gchar *string, const gchar *chars)
1855 const gchar *r;
1856 gchar *w = string;
1858 g_return_val_if_fail(string, NULL);
1859 if (G_UNLIKELY(EMPTY(chars)))
1860 return string;
1862 foreach_str(r, string)
1864 if (!strchr(chars, *r))
1865 *w++ = *r;
1867 *w = 0x0;
1868 return string;
1872 /* Gets list of sorted filenames with no path and no duplicates from user and system config */
1873 GSList *utils_get_config_files(const gchar *subdir)
1875 gchar *path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, subdir, NULL);
1876 GSList *list = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1877 GSList *syslist, *node;
1879 if (!list)
1881 utils_mkdir(path, FALSE);
1883 SETPTR(path, g_build_path(G_DIR_SEPARATOR_S, app->datadir, subdir, NULL));
1884 syslist = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1885 /* merge lists */
1886 list = g_slist_concat(list, syslist);
1888 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1889 /* remove duplicates (next to each other after sorting) */
1890 foreach_slist(node, list)
1892 if (node->next && utils_str_equal(node->next->data, node->data))
1894 GSList *old = node->next;
1896 g_free(old->data);
1897 node->next = old->next;
1898 g_slist_free1(old);
1901 g_free(path);
1902 return list;
1906 /* Suffix can be NULL or a string which should be appended to the Help URL like
1907 * an anchor link, e.g. "#some_anchor". */
1908 gchar *utils_get_help_url(const gchar *suffix)
1910 gint skip;
1911 gchar *uri;
1913 #ifdef G_OS_WIN32
1914 skip = 8;
1915 uri = g_strconcat("file:///", app->docdir, "/index.html", NULL);
1916 g_strdelimit(uri, "\\", '/'); /* replace '\\' by '/' */
1917 #else
1918 skip = 7;
1919 uri = g_strconcat("file://", app->docdir, "/index.html", NULL);
1920 #endif
1922 if (! g_file_test(uri + skip, G_FILE_TEST_IS_REGULAR))
1923 { /* fall back to online documentation if it is not found on the hard disk */
1924 g_free(uri);
1925 uri = g_strconcat(GEANY_HOMEPAGE, "manual/", VERSION, "/index.html", NULL);
1928 if (suffix != NULL)
1930 SETPTR(uri, g_strconcat(uri, suffix, NULL));
1933 return uri;
1937 static gboolean str_in_array(const gchar **haystack, const gchar *needle)
1939 const gchar **p;
1941 for (p = haystack; *p != NULL; ++p)
1943 if (utils_str_equal(*p, needle))
1944 return TRUE;
1946 return FALSE;
1951 * Copies the current environment into a new array.
1952 * @a exclude_vars is a @c NULL-terminated array of variable names which should be not copied.
1953 * All further arguments are key, value pairs of variables which should be added to
1954 * the environment.
1956 * The argument list must be @c NULL-terminated.
1958 * @param exclude_vars @c NULL-terminated array of variable names to exclude.
1959 * @param first_varname Name of the first variable to copy into the new array.
1960 * @param ... Key-value pairs of variable names and values, @c NULL-terminated.
1962 * @return @transfer{full} The new environment array. Use @c g_strfreev() to free it.
1964 GEANY_API_SYMBOL
1965 gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...)
1967 gchar **result;
1968 gchar **p;
1969 gchar **env;
1970 va_list args;
1971 const gchar *key, *value;
1972 guint n, o;
1974 /* count the additional variables */
1975 va_start(args, first_varname);
1976 for (o = 1; va_arg(args, gchar*) != NULL; o++);
1977 va_end(args);
1978 /* the passed arguments should be even (key, value pairs) */
1979 g_return_val_if_fail(o % 2 == 0, NULL);
1981 o /= 2;
1983 /* get all the environ variables */
1984 env = g_listenv();
1986 /* create an array large enough to hold the new environment */
1987 n = g_strv_length(env);
1988 /* 'n + o + 1' could leak a little bit when exclude_vars is set */
1989 result = g_new(gchar *, n + o + 1);
1991 /* copy the environment */
1992 for (n = 0, p = env; *p != NULL; ++p)
1994 /* copy the variable */
1995 value = g_getenv(*p);
1996 if (G_LIKELY(value != NULL))
1998 /* skip excluded variables */
1999 if (exclude_vars != NULL && str_in_array(exclude_vars, *p))
2000 continue;
2002 result[n++] = g_strconcat(*p, "=", value, NULL);
2005 g_strfreev(env);
2007 /* now add additional variables */
2008 va_start(args, first_varname);
2009 key = first_varname;
2010 value = va_arg(args, gchar*);
2011 while (key != NULL)
2013 result[n++] = g_strconcat(key, "=", value, NULL);
2015 key = va_arg(args, gchar*);
2016 if (key == NULL)
2017 break;
2018 value = va_arg(args, gchar*);
2020 va_end(args);
2022 result[n] = NULL;
2024 return result;
2028 /* Joins @a first and @a second into a new string vector, freeing the originals.
2029 * The original contents are reused. */
2030 gchar **utils_strv_join(gchar **first, gchar **second)
2032 gchar **strv;
2033 gchar **rptr, **wptr;
2035 if (!first)
2036 return second;
2037 if (!second)
2038 return first;
2040 strv = g_new0(gchar*, g_strv_length(first) + g_strv_length(second) + 1);
2041 wptr = strv;
2043 foreach_strv(rptr, first)
2044 *wptr++ = *rptr;
2045 foreach_strv(rptr, second)
2046 *wptr++ = *rptr;
2048 g_free(first);
2049 g_free(second);
2050 return strv;
2054 /* Try to parse a date using g_date_set_parse(). It doesn't take any format hint,
2055 * obviously g_date_set_parse() uses some magic.
2056 * The returned GDate object must be freed. */
2057 GDate *utils_parse_date(const gchar *input)
2059 GDate *date = g_date_new();
2061 g_date_set_parse(date, input);
2063 if (g_date_valid(date))
2064 return date;
2066 g_date_free(date);
2067 return NULL;
2071 gchar *utils_parse_and_format_build_date(const gchar *input)
2073 gchar date_buf[255];
2074 GDate *date = utils_parse_date(input);
2076 if (date != NULL)
2078 g_date_strftime(date_buf, sizeof(date_buf), GEANY_TEMPLATES_FORMAT_DATE, date);
2079 g_date_free(date);
2080 return g_strdup(date_buf);
2083 return g_strdup(input);
2087 gchar *utils_get_user_config_dir(void)
2089 #ifdef G_OS_WIN32
2090 return win32_get_user_config_dir();
2091 #else
2092 return g_build_filename(g_get_user_config_dir(), "geany", NULL);
2093 #endif
2097 static gboolean is_osx_bundle(void)
2099 #ifdef MAC_INTEGRATION
2100 gchar *bundle_id = gtkosx_application_get_bundle_id();
2101 if (bundle_id)
2103 g_free(bundle_id);
2104 return TRUE;
2106 #endif
2107 return FALSE;
2111 const gchar *utils_resource_dir(GeanyResourceDirType type)
2113 static const gchar *resdirs[RESOURCE_DIR_COUNT] = {NULL};
2115 if (!resdirs[RESOURCE_DIR_DATA])
2117 #ifdef G_OS_WIN32
2118 gchar *prefix = win32_get_installation_dir();
2120 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "data", NULL);
2121 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2122 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2123 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2124 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2125 g_free(prefix);
2126 #else
2127 if (is_osx_bundle())
2129 # ifdef MAC_INTEGRATION
2130 gchar *prefix = gtkosx_application_get_resource_path();
2132 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "share", "geany", NULL);
2133 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2134 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2135 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2136 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2137 g_free(prefix);
2138 # endif
2140 else
2142 resdirs[RESOURCE_DIR_DATA] = g_build_filename(GEANY_DATADIR, "geany", NULL);
2143 resdirs[RESOURCE_DIR_ICON] = g_build_filename(GEANY_DATADIR, "icons", NULL);
2144 resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL);
2145 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL);
2146 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL);
2148 #endif
2151 return resdirs[type];
2155 void utils_start_new_geany_instance(const gchar *doc_path)
2157 const gchar *command = is_osx_bundle() ? "open" : "geany";
2158 gchar *exec_path = g_find_program_in_path(command);
2160 if (exec_path)
2162 GError *err = NULL;
2163 const gchar *argv[6]; // max args + 1
2164 gint argc = 0;
2166 argv[argc++] = exec_path;
2167 if (is_osx_bundle())
2169 argv[argc++] = "-n";
2170 argv[argc++] = "-a";
2171 argv[argc++] = "Geany";
2172 argv[argc++] = doc_path;
2174 else
2176 argv[argc++] = "-i";
2177 argv[argc++] = doc_path;
2179 argv[argc] = NULL;
2181 if (!utils_spawn_async(NULL, (gchar**) argv, NULL, 0, NULL, NULL, NULL, &err))
2183 g_printerr("Unable to open new window: %s", err->message);
2184 g_error_free(err);
2186 g_free(exec_path);
2188 else
2189 g_printerr("Unable to find 'geany'");