c family: Add support for digraphs
[geany-mirror.git] / src / utils.c
blob4fc9f16a59c5f814daf7ac3021474443e2227de8
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 "sciwrappers.h"
37 #include "spawn.h"
38 #include "support.h"
39 #include "templates.h"
40 #include "ui_utils.h"
41 #include "win32.h"
42 #include "osx.h"
44 #include <stdlib.h>
45 #include <ctype.h>
46 #include <math.h>
47 #include <unistd.h>
48 #include <string.h>
49 #include <errno.h>
50 #include <stdarg.h>
52 #ifdef HAVE_SYS_STAT_H
53 # include <sys/stat.h>
54 #endif
55 #ifdef HAVE_SYS_TYPES_H
56 # include <sys/types.h>
57 #endif
59 #include <glib/gstdio.h>
60 #include <gio/gio.h>
63 /**
64 * Tries to open the given URI in a browser.
65 * On Windows, the system's default browser is opened.
66 * On non-Windows systems, the browser command set in the preferences dialog is used. In case
67 * that fails or it is unset, the user is asked to correct or fill it.
69 * @param uri The URI to open in the web browser.
71 * @since 0.16
72 **/
73 GEANY_API_SYMBOL
74 void utils_open_browser(const gchar *uri)
76 #ifdef G_OS_WIN32
77 g_return_if_fail(uri != NULL);
78 win32_open_browser(uri);
79 #else
80 gchar *argv[2] = { (gchar *) uri, NULL };
82 g_return_if_fail(uri != NULL);
84 while (!spawn_async(NULL, tool_prefs.browser_cmd, argv, NULL, NULL, NULL))
86 gchar *new_cmd = dialogs_show_input(_("Select Browser"), GTK_WINDOW(main_widgets.window),
87 _("Failed to spawn the configured browser command. "
88 "Please correct it or enter another one."),
89 tool_prefs.browser_cmd);
91 if (new_cmd == NULL) /* user canceled */
92 break;
94 SETPTR(tool_prefs.browser_cmd, new_cmd);
96 #endif
100 /* taken from anjuta, to determine the EOL mode of the file */
101 gint utils_get_line_endings(const gchar* buffer, gsize size)
103 gsize i;
104 guint cr, lf, crlf, max_mode;
105 gint mode;
107 cr = lf = crlf = 0;
109 for (i = 0; i < size ; i++)
111 if (buffer[i] == 0x0a)
113 /* LF */
114 lf++;
116 else if (buffer[i] == 0x0d)
118 if (i >= (size - 1))
120 /* Last char, CR */
121 cr++;
123 else
125 if (buffer[i + 1] != 0x0a)
127 /* CR */
128 cr++;
130 else
132 /* CRLF */
133 crlf++;
135 i++;
140 /* Vote for the maximum */
141 mode = SC_EOL_LF;
142 max_mode = lf;
143 if (crlf > max_mode)
145 mode = SC_EOL_CRLF;
146 max_mode = crlf;
148 if (cr > max_mode)
150 mode = SC_EOL_CR;
151 max_mode = cr;
154 return mode;
158 gboolean utils_isbrace(gchar c, gboolean include_angles)
160 switch (c)
162 case '<':
163 case '>':
164 return include_angles;
166 case '(':
167 case ')':
168 case '{':
169 case '}':
170 case '[':
171 case ']': return TRUE;
172 default: return FALSE;
177 gboolean utils_is_opening_brace(gchar c, gboolean include_angles)
179 switch (c)
181 case '<':
182 return include_angles;
184 case '(':
185 case '{':
186 case '[': return TRUE;
187 default: return FALSE;
193 * Writes @a text into a file named @a filename.
194 * If the file doesn't exist, it will be created.
195 * If it already exists, it will be overwritten.
197 * @warning You should use @c g_file_set_contents() instead if you don't need
198 * file permissions and other metadata to be preserved, as that always handles
199 * disk exhaustion safely.
201 * @param filename The filename of the file to write, in locale encoding.
202 * @param text The text to write into the file.
204 * @return 0 if the file was successfully written, otherwise the @c errno of the
205 * failed operation is returned.
207 GEANY_API_SYMBOL
208 gint utils_write_file(const gchar *filename, const gchar *text)
210 g_return_val_if_fail(filename != NULL, ENOENT);
211 g_return_val_if_fail(text != NULL, EINVAL);
213 if (file_prefs.use_safe_file_saving)
215 GError *error = NULL;
216 if (! g_file_set_contents(filename, text, -1, &error))
218 geany_debug("%s: could not write to file %s (%s)", G_STRFUNC, filename, error->message);
219 g_error_free(error);
220 return EIO;
223 else
225 FILE *fp;
226 gsize bytes_written, len;
227 gboolean fail = FALSE;
229 if (filename == NULL)
230 return ENOENT;
232 len = strlen(text);
233 errno = 0;
234 fp = g_fopen(filename, "w");
235 if (fp == NULL)
236 fail = TRUE;
237 else
239 bytes_written = fwrite(text, sizeof(gchar), len, fp);
241 if (len != bytes_written)
243 fail = TRUE;
244 geany_debug(
245 "utils_write_file(): written only %"G_GSIZE_FORMAT" bytes, had to write %"G_GSIZE_FORMAT" bytes to %s",
246 bytes_written, len, filename);
248 if (fclose(fp) != 0)
249 fail = TRUE;
251 if (fail)
253 geany_debug("utils_write_file(): could not write to file %s (%s)",
254 filename, g_strerror(errno));
255 return FALLBACK(errno, EIO);
258 return 0;
262 /** Searches backward through @a size bytes looking for a '<'.
263 * @param sel .
264 * @param size .
265 * @return The tag name (newly allocated) or @c NULL if no opening tag was found.
267 GEANY_API_SYMBOL
268 gchar *utils_find_open_xml_tag(const gchar sel[], gint size)
270 const gchar *cur, *begin;
271 gsize len;
273 cur = utils_find_open_xml_tag_pos(sel, size);
274 if (cur == NULL)
275 return NULL;
277 cur++; /* skip the bracket */
278 begin = cur;
279 while (strchr(":_-.", *cur) || isalnum(*cur))
280 cur++;
282 len = (gsize)(cur - begin);
283 return len ? g_strndup(begin, len) : NULL;
287 /** Searches backward through @a size bytes looking for a '<'.
288 * @param sel .
289 * @param size .
290 * @return pointer to '<' of the found opening tag within @a sel, or @c NULL if no opening tag was found.
292 GEANY_API_SYMBOL
293 const gchar *utils_find_open_xml_tag_pos(const gchar sel[], gint size)
295 /* stolen from anjuta and modified */
296 const gchar *begin, *cur;
298 if (G_UNLIKELY(size < 3))
299 { /* Smallest tag is "<p>" which is 3 characters */
300 return NULL;
302 begin = &sel[0];
303 cur = &sel[size - 1];
305 /* Skip to the character before the closing brace */
306 while (cur > begin)
308 if (*cur == '>')
309 break;
310 --cur;
312 --cur;
313 /* skip whitespace */
314 while (cur > begin && isspace(*cur))
315 cur--;
316 if (*cur == '/')
317 return NULL; /* we found a short tag which doesn't need to be closed */
318 while (cur > begin)
320 if (*cur == '<')
321 break;
322 /* exit immediately if such non-valid XML/HTML is detected, e.g. "<script>if a >" */
323 else if (*cur == '>')
324 break;
325 --cur;
328 /* if the found tag is an opening, not a closing tag or empty <> */
329 if (*cur == '<' && *(cur + 1) != '/' && *(cur + 1) != '>')
330 return cur;
332 return NULL;
336 /* Returns true if tag_name is a self-closing tag */
337 gboolean utils_is_short_html_tag(const gchar *tag_name)
339 const gchar names[][20] = {
340 "area",
341 "base",
342 "basefont", /* < or not < */
343 "br",
344 "col",
345 "command",
346 "embed",
347 "frame",
348 "hr",
349 "img",
350 "input",
351 "keygen",
352 "link",
353 "meta",
354 "param",
355 "source",
356 "track",
357 "wbr"
360 if (tag_name)
362 if (bsearch(tag_name, names, G_N_ELEMENTS(names), 20,
363 (GCompareFunc)g_ascii_strcasecmp))
364 return TRUE;
366 return FALSE;
370 const gchar *utils_get_eol_name(gint eol_mode)
372 switch (eol_mode)
374 case SC_EOL_CRLF: return _("Windows (CRLF)"); break;
375 case SC_EOL_CR: return _("Classic Mac (CR)"); break;
376 default: return _("Unix (LF)"); break;
381 const gchar *utils_get_eol_short_name(gint eol_mode)
383 switch (eol_mode)
385 case SC_EOL_CRLF: return _("CRLF"); break;
386 case SC_EOL_CR: return _("CR"); break;
387 default: return _("LF"); break;
392 const gchar *utils_get_eol_char(gint eol_mode)
394 switch (eol_mode)
396 case SC_EOL_CRLF: return "\r\n"; break;
397 case SC_EOL_CR: return "\r"; break;
398 default: return "\n"; break;
403 /* Converts line endings to @a target_eol_mode. */
404 void utils_ensure_same_eol_characters(GString *string, gint target_eol_mode)
406 const gchar *eol_str = utils_get_eol_char(target_eol_mode);
408 /* first convert data to LF only */
409 utils_string_replace_all(string, "\r\n", "\n");
410 utils_string_replace_all(string, "\r", "\n");
412 if (target_eol_mode == SC_EOL_LF)
413 return;
415 /* now convert to desired line endings */
416 utils_string_replace_all(string, "\n", eol_str);
420 gboolean utils_atob(const gchar *str)
422 if (G_UNLIKELY(str == NULL))
423 return FALSE;
424 else if (strcmp(str, "TRUE") == 0 || strcmp(str, "true") == 0)
425 return TRUE;
426 return FALSE;
430 /* NULL-safe version of g_path_is_absolute(). */
431 gboolean utils_is_absolute_path(const gchar *path)
433 if (G_UNLIKELY(EMPTY(path)))
434 return FALSE;
436 return g_path_is_absolute(path);
440 /* Skips root if path is absolute, do nothing otherwise.
441 * This is a relative-safe version of g_path_skip_root().
443 const gchar *utils_path_skip_root(const gchar *path)
445 const gchar *path_relative;
447 path_relative = g_path_skip_root(path);
449 return (path_relative != NULL) ? path_relative : path;
453 gdouble utils_scale_round(gdouble val, gdouble factor)
455 /*val = floor(val * factor + 0.5);*/
456 val = floor(val);
457 val = MAX(val, 0);
458 val = MIN(val, factor);
460 return val;
464 /* like g_utf8_strdown() but if @str is not valid UTF8, convert it from locale first.
465 * returns NULL on charset conversion failure */
466 static gchar *utf8_strdown(const gchar *str)
468 gchar *down;
470 if (g_utf8_validate(str, -1, NULL))
471 down = g_utf8_strdown(str, -1);
472 else
474 down = g_locale_to_utf8(str, -1, NULL, NULL, NULL);
475 if (down)
476 SETPTR(down, g_utf8_strdown(down, -1));
479 return down;
484 * A replacement function for g_strncasecmp() to compare strings case-insensitive.
485 * It converts both strings into lowercase using g_utf8_strdown() and then compare
486 * both strings using strcmp().
487 * This is not completely accurate regarding locale-specific case sorting rules
488 * but seems to be a good compromise between correctness and performance.
490 * The input strings should be in UTF-8 or locale encoding.
492 * @param s1 Pointer to first string or @c NULL.
493 * @param s2 Pointer to second string or @c NULL.
495 * @return an integer less than, equal to, or greater than zero if @a s1 is found, respectively,
496 * to be less than, to match, or to be greater than @a s2.
498 * @since 0.16
500 GEANY_API_SYMBOL
501 gint utils_str_casecmp(const gchar *s1, const gchar *s2)
503 gchar *tmp1, *tmp2;
504 gint result;
506 g_return_val_if_fail(s1 != NULL, 1);
507 g_return_val_if_fail(s2 != NULL, -1);
509 /* ensure strings are UTF-8 and lowercase */
510 tmp1 = utf8_strdown(s1);
511 if (! tmp1)
512 return 1;
513 tmp2 = utf8_strdown(s2);
514 if (! tmp2)
516 g_free(tmp1);
517 return -1;
520 /* compare */
521 result = strcmp(tmp1, tmp2);
523 g_free(tmp1);
524 g_free(tmp2);
525 return result;
530 * Truncates the input string to a given length.
531 * Characters are removed from the middle of the string, so the start and the end of string
532 * won't change.
534 * @param string Input string.
535 * @param truncate_length The length in characters of the resulting string.
537 * @return A copy of @a string which is truncated to @a truncate_length characters,
538 * should be freed when no longer needed.
540 * @since 0.17
542 /* This following function is taken from Gedit. */
543 GEANY_API_SYMBOL
544 gchar *utils_str_middle_truncate(const gchar *string, guint truncate_length)
546 GString *truncated;
547 guint length;
548 guint n_chars;
549 guint num_left_chars;
550 guint right_offset;
551 guint delimiter_length;
552 const gchar *delimiter = "\342\200\246";
554 g_return_val_if_fail(string != NULL, NULL);
556 length = strlen(string);
558 g_return_val_if_fail(g_utf8_validate(string, length, NULL), NULL);
560 /* It doesn't make sense to truncate strings to less than the size of the delimiter plus 2
561 * characters (one on each side) */
562 delimiter_length = g_utf8_strlen(delimiter, -1);
563 if (truncate_length < (delimiter_length + 2))
564 return g_strdup(string);
566 n_chars = g_utf8_strlen(string, length);
568 /* Make sure the string is not already small enough. */
569 if (n_chars <= truncate_length)
570 return g_strdup (string);
572 /* Find the 'middle' where the truncation will occur. */
573 num_left_chars = (truncate_length - delimiter_length) / 2;
574 right_offset = n_chars - truncate_length + num_left_chars + delimiter_length;
576 truncated = g_string_new_len(string, g_utf8_offset_to_pointer(string, num_left_chars) - string);
577 g_string_append(truncated, delimiter);
578 g_string_append(truncated, g_utf8_offset_to_pointer(string, right_offset));
580 return g_string_free(truncated, FALSE);
585 * @c NULL-safe string comparison. Returns @c TRUE if both @a a and @a b are @c NULL
586 * or if @a a and @a b refer to valid strings which are equal.
588 * @param a Pointer to first string or @c NULL.
589 * @param b Pointer to second string or @c NULL.
591 * @return @c TRUE if @a a equals @a b, else @c FALSE.
593 GEANY_API_SYMBOL
594 gboolean utils_str_equal(const gchar *a, const gchar *b)
596 /* (taken from libexo from os-cillation) */
597 if (a == NULL && b == NULL) return TRUE;
598 else if (a == NULL || b == NULL) return FALSE;
600 return strcmp(a, b) == 0;
605 * Removes the extension from @a filename and return the result in a newly allocated string.
607 * @param filename The filename to operate on.
609 * @return A newly-allocated string, should be freed when no longer needed.
611 GEANY_API_SYMBOL
612 gchar *utils_remove_ext_from_filename(const gchar *filename)
614 gchar *last_dot;
615 gchar *result;
616 gsize len;
618 g_return_val_if_fail(filename != NULL, NULL);
620 last_dot = strrchr(filename, '.');
621 if (! last_dot)
622 return g_strdup(filename);
624 len = (gsize) (last_dot - filename);
625 result = g_malloc(len + 1);
626 memcpy(result, filename, len);
627 result[len] = 0;
629 return result;
633 gchar utils_brace_opposite(gchar ch)
635 switch (ch)
637 case '(': return ')';
638 case ')': return '(';
639 case '[': return ']';
640 case ']': return '[';
641 case '{': return '}';
642 case '}': return '{';
643 case '<': return '>';
644 case '>': return '<';
645 default: return '\0';
650 /* Checks whether the given file can be written. locale_filename is expected in locale encoding.
651 * Returns 0 if it can be written, otherwise it returns errno */
652 gint utils_is_file_writable(const gchar *locale_filename)
654 gchar *file;
655 gint ret;
657 if (! g_file_test(locale_filename, G_FILE_TEST_EXISTS) &&
658 ! g_file_test(locale_filename, G_FILE_TEST_IS_DIR))
659 /* get the file's directory to check for write permission if it doesn't yet exist */
660 file = g_path_get_dirname(locale_filename);
661 else
662 file = g_strdup(locale_filename);
664 #ifdef G_OS_WIN32
665 /* use _waccess on Windows, access() doesn't accept special characters */
666 ret = win32_check_write_permission(file);
667 #else
669 /* access set also errno to "FILE NOT FOUND" even if locale_filename is writeable, so use
670 * errno only when access() explicitly returns an error */
671 if (access(file, R_OK | W_OK) != 0)
672 ret = errno;
673 else
674 ret = 0;
675 #endif
676 g_free(file);
677 return ret;
681 /* Replaces all occurrences of needle in haystack with replacement.
682 * Warning: *haystack must be a heap address; it may be freed and reassigned.
683 * Note: utils_string_replace_all() will always be faster when @a replacement is longer
684 * than @a needle.
685 * All strings have to be NULL-terminated.
686 * See utils_string_replace_all() for details. */
687 void utils_str_replace_all(gchar **haystack, const gchar *needle, const gchar *replacement)
689 GString *str;
691 g_return_if_fail(*haystack != NULL);
693 str = g_string_new(*haystack);
695 g_free(*haystack);
696 utils_string_replace_all(str, needle, replacement);
698 *haystack = g_string_free(str, FALSE);
702 gint utils_strpos(const gchar *haystack, const gchar *needle)
704 const gchar *sub;
706 if (! *needle)
707 return -1;
709 sub = strstr(haystack, needle);
710 if (! sub)
711 return -1;
713 return sub - haystack;
718 * Retrieves a formatted date/time string from strftime().
719 * This function should be preferred to directly calling strftime() since this function
720 * works on UTF-8 encoded strings.
722 * @param format The format string to pass to strftime(3). See the strftime(3)
723 * documentation for details, in UTF-8 encoding.
724 * @param time_to_use The date/time to use, in time_t format or NULL to use the current time.
726 * @return A newly-allocated string, should be freed when no longer needed.
728 * @since 0.16
730 GEANY_API_SYMBOL
731 gchar *utils_get_date_time(const gchar *format, time_t *time_to_use)
733 const struct tm *tm;
734 static gchar date[1024];
735 gchar *locale_format;
736 gsize len;
738 g_return_val_if_fail(format != NULL, NULL);
740 if (! g_utf8_validate(format, -1, NULL))
742 locale_format = g_locale_from_utf8(format, -1, NULL, NULL, NULL);
743 if (locale_format == NULL)
744 return NULL;
746 else
747 locale_format = g_strdup(format);
749 if (time_to_use != NULL)
750 tm = localtime(time_to_use);
751 else
753 time_t tp = time(NULL);
754 tm = localtime(&tp);
757 len = strftime(date, 1024, locale_format, tm);
758 g_free(locale_format);
759 if (len == 0)
760 return NULL;
762 if (! g_utf8_validate(date, len, NULL))
763 return g_locale_to_utf8(date, len, NULL, NULL, NULL);
764 else
765 return g_strdup(date);
769 gchar *utils_get_initials(const gchar *name)
771 gint i = 1, j = 1;
772 gchar *initials = g_malloc0(5);
774 initials[0] = name[0];
775 while (name[i] != '\0' && j < 4)
777 if (name[i] == ' ' && name[i + 1] != ' ')
779 initials[j++] = name[i + 1];
781 i++;
783 return initials;
788 * Wraps g_key_file_get_integer() to add a default value argument.
790 * @param config A GKeyFile object.
791 * @param section The group name to look in for the key.
792 * @param key The key to find.
793 * @param default_value The default value which will be returned when @a section or @a key
794 * don't exist.
796 * @return The value associated with @a key as an integer, or the given default value if the value
797 * could not be retrieved.
799 GEANY_API_SYMBOL
800 gint utils_get_setting_integer(GKeyFile *config, const gchar *section, const gchar *key,
801 const gint default_value)
803 gint tmp;
804 GError *error = NULL;
806 g_return_val_if_fail(config, default_value);
808 tmp = g_key_file_get_integer(config, section, key, &error);
809 if (error)
811 g_error_free(error);
812 return default_value;
814 return tmp;
819 * Wraps g_key_file_get_boolean() to add a default value argument.
821 * @param config A GKeyFile object.
822 * @param section The group name to look in for the key.
823 * @param key The key to find.
824 * @param default_value The default value which will be returned when @c section or @c key
825 * don't exist.
827 * @return The value associated with @a key as a boolean, or the given default value if the value
828 * could not be retrieved.
830 GEANY_API_SYMBOL
831 gboolean utils_get_setting_boolean(GKeyFile *config, const gchar *section, const gchar *key,
832 const gboolean default_value)
834 gboolean tmp;
835 GError *error = NULL;
837 g_return_val_if_fail(config, default_value);
839 tmp = g_key_file_get_boolean(config, section, key, &error);
840 if (error)
842 g_error_free(error);
843 return default_value;
845 return tmp;
850 * Wraps g_key_file_get_string() to add a default value argument.
852 * @param config A GKeyFile object.
853 * @param section The group name to look in for the key.
854 * @param key The key to find.
855 * @param default_value The default value which will be returned when @a section or @a key
856 * don't exist.
858 * @return A newly allocated string, either the value for @a key or a copy of the given
859 * default value if it could not be retrieved.
861 GEANY_API_SYMBOL
862 gchar *utils_get_setting_string(GKeyFile *config, const gchar *section, const gchar *key,
863 const gchar *default_value)
865 gchar *tmp;
867 g_return_val_if_fail(config, g_strdup(default_value));
869 tmp = g_key_file_get_string(config, section, key, NULL);
870 if (!tmp)
872 return g_strdup(default_value);
874 return tmp;
878 gchar *utils_get_hex_from_color(GdkColor *color)
880 g_return_val_if_fail(color != NULL, NULL);
882 return g_strdup_printf("#%02X%02X%02X",
883 (guint) (utils_scale_round(color->red / 256, 255)),
884 (guint) (utils_scale_round(color->green / 256, 255)),
885 (guint) (utils_scale_round(color->blue / 256, 255)));
889 /* Get directory from current file in the notebook.
890 * Returns dir string that should be freed or NULL, depending on whether current file is valid.
891 * Returned string is in UTF-8 encoding */
892 gchar *utils_get_current_file_dir_utf8(void)
894 GeanyDocument *doc = document_get_current();
896 if (doc != NULL)
898 /* get current filename */
899 const gchar *cur_fname = doc->file_name;
901 if (cur_fname != NULL)
903 /* get folder part from current filename */
904 return g_path_get_dirname(cur_fname); /* returns "." if no path */
908 return NULL; /* no file open */
912 /* very simple convenience function */
913 void utils_beep(void)
915 if (prefs.beep_on_errors)
916 gdk_beep();
920 /* taken from busybox, thanks */
921 gchar *utils_make_human_readable_str(guint64 size, gulong block_size,
922 gulong display_unit)
924 /* The code will adjust for additional (appended) units. */
925 static const gchar zero_and_units[] = { '0', 0, 'K', 'M', 'G', 'T' };
926 static const gchar fmt[] = "%Lu %c%c";
927 static const gchar fmt_tenths[] = "%Lu.%d %c%c";
929 guint64 val;
930 gint frac;
931 const gchar *u;
932 const gchar *f;
934 u = zero_and_units;
935 f = fmt;
936 frac = 0;
938 val = size * block_size;
939 if (val == 0)
940 return g_strdup(u);
942 if (display_unit)
944 val += display_unit/2; /* Deal with rounding. */
945 val /= display_unit; /* Don't combine with the line above!!! */
947 else
949 ++u;
950 while ((val >= 1024) && (u < zero_and_units + sizeof(zero_and_units) - 1))
952 f = fmt_tenths;
953 ++u;
954 frac = ((((gint)(val % 1024)) * 10) + (1024 / 2)) / 1024;
955 val /= 1024;
957 if (frac >= 10)
958 { /* We need to round up here. */
959 ++val;
960 frac = 0;
964 /* If f==fmt then 'frac' and 'u' are ignored. */
965 return g_strdup_printf(f, val, frac, *u, 'b');
969 /* converts a color representation using gdk_color_parse(), with additional
970 * support of the "0x" prefix as a synonym for "#" */
971 gboolean utils_parse_color(const gchar *spec, GdkColor *color)
973 gchar buf[64] = {0};
975 g_return_val_if_fail(spec != NULL, -1);
977 if (spec[0] == '0' && (spec[1] == 'x' || spec[1] == 'X'))
979 /* convert to # format for GDK to understand it */
980 buf[0] = '#';
981 strncpy(buf + 1, spec + 2, sizeof(buf) - 2);
982 spec = buf;
985 return gdk_color_parse(spec, color);
989 /* converts a GdkColor to the packed 24 bits BGR format, as understood by Scintilla
990 * returns a 24 bits BGR color, or -1 on failure */
991 gint utils_color_to_bgr(const GdkColor *c)
993 g_return_val_if_fail(c != NULL, -1);
994 return (c->red / 256) | ((c->green / 256) << 8) | ((c->blue / 256) << 16);
998 /* parses @p spec using utils_parse_color() and convert it to 24 bits BGR using
999 * utils_color_to_bgr() */
1000 gint utils_parse_color_to_bgr(const gchar *spec)
1002 GdkColor color;
1003 if (utils_parse_color(spec, &color))
1004 return utils_color_to_bgr(&color);
1005 else
1006 return -1;
1010 /* Returns: newly allocated string with the current time formatted HH:MM:SS. */
1011 gchar *utils_get_current_time_string(void)
1013 const time_t tp = time(NULL);
1014 const struct tm *tmval = localtime(&tp);
1015 gchar *result = g_malloc0(9);
1017 strftime(result, 9, "%H:%M:%S", tmval);
1018 result[8] = '\0';
1019 return result;
1023 GIOChannel *utils_set_up_io_channel(
1024 gint fd, GIOCondition cond, gboolean nblock, GIOFunc func, gpointer data)
1026 GIOChannel *ioc;
1027 /*const gchar *encoding;*/
1029 #ifdef G_OS_WIN32
1030 ioc = g_io_channel_win32_new_fd(fd);
1031 #else
1032 ioc = g_io_channel_unix_new(fd);
1033 #endif
1035 if (nblock)
1036 g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
1038 g_io_channel_set_encoding(ioc, NULL, NULL);
1040 if (! g_get_charset(&encoding))
1041 { // hope this works reliably
1042 GError *error = NULL;
1043 g_io_channel_set_encoding(ioc, encoding, &error);
1044 if (error)
1046 geany_debug("%s: %s", G_STRFUNC, error->message);
1047 g_error_free(error);
1048 return ioc;
1052 /* "auto-close" ;-) */
1053 g_io_channel_set_close_on_unref(ioc, TRUE);
1055 g_io_add_watch(ioc, cond, func, data);
1056 g_io_channel_unref(ioc);
1058 return ioc;
1062 gchar **utils_read_file_in_array(const gchar *filename)
1064 gchar **result = NULL;
1065 gchar *data;
1067 g_return_val_if_fail(filename != NULL, NULL);
1069 g_file_get_contents(filename, &data, NULL, NULL);
1071 if (data != NULL)
1073 result = g_strsplit_set(data, "\r\n", -1);
1074 g_free(data);
1077 return result;
1081 /* Contributed by Stefan Oltmanns, thanks.
1082 * Replaces \\, \r, \n, \t and \uXXX by their real counterparts.
1083 * keep_backslash is used for regex strings to leave '\\' and '\?' in place */
1084 gboolean utils_str_replace_escape(gchar *string, gboolean keep_backslash)
1086 gsize i, j, len;
1087 guint unicodechar;
1089 g_return_val_if_fail(string != NULL, FALSE);
1091 j = 0;
1092 len = strlen(string);
1093 for (i = 0; i < len; i++)
1095 if (string[i]=='\\')
1097 if (i++ >= strlen(string))
1099 return FALSE;
1101 switch (string[i])
1103 case '\\':
1104 if (keep_backslash)
1105 string[j++] = '\\';
1106 string[j] = '\\';
1107 break;
1108 case 'n':
1109 string[j] = '\n';
1110 break;
1111 case 'r':
1112 string[j] = '\r';
1113 break;
1114 case 't':
1115 string[j] = '\t';
1116 break;
1117 #if 0
1118 case 'x': /* Warning: May produce illegal utf-8 string! */
1119 i += 2;
1120 if (i >= strlen(string))
1122 return FALSE;
1124 if (isdigit(string[i - 1])) string[j] = string[i - 1] - 48;
1125 else if (isxdigit(string[i - 1])) string[j] = tolower(string[i - 1])-87;
1126 else return FALSE;
1127 string[j] <<= 4;
1128 if (isdigit(string[i])) string[j] |= string[i] - 48;
1129 else if (isxdigit(string[i])) string[j] |= tolower(string[i])-87;
1130 else return FALSE;
1131 break;
1132 #endif
1133 case 'u':
1135 i += 2;
1136 if (i >= strlen(string))
1138 return FALSE;
1140 if (isdigit(string[i - 1])) unicodechar = string[i - 1] - 48;
1141 else if (isxdigit(string[i - 1])) unicodechar = tolower(string[i - 1])-87;
1142 else return FALSE;
1143 unicodechar <<= 4;
1144 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1145 else if (isxdigit(string[i])) unicodechar |= tolower(string[i])-87;
1146 else return FALSE;
1147 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1148 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1150 i += 2;
1151 unicodechar <<= 8;
1152 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1153 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1154 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1155 else unicodechar |= tolower(string[i])-87;
1157 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1158 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1160 i += 2;
1161 unicodechar <<= 8;
1162 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1163 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1164 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1165 else unicodechar |= tolower(string[i])-87;
1167 if (unicodechar < 0x80)
1169 string[j] = unicodechar;
1171 else if (unicodechar < 0x800)
1173 string[j] = (unsigned char) ((unicodechar >> 6) | 0xC0);
1174 j++;
1175 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1177 else if (unicodechar < 0x10000)
1179 string[j] = (unsigned char) ((unicodechar >> 12) | 0xE0);
1180 j++;
1181 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1182 j++;
1183 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1185 else if (unicodechar < 0x110000) /* more chars are not allowed in unicode */
1187 string[j] = (unsigned char) ((unicodechar >> 18) | 0xF0);
1188 j++;
1189 string[j] = (unsigned char) (((unicodechar >> 12) & 0x3F) | 0x80);
1190 j++;
1191 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1192 j++;
1193 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1195 else
1197 return FALSE;
1199 break;
1201 default:
1202 /* unnecessary escapes are allowed */
1203 if (keep_backslash)
1204 string[j++] = '\\';
1205 string[j] = string[i];
1208 else
1210 string[j] = string[i];
1212 j++;
1214 while (j < i)
1216 string[j] = 0;
1217 j++;
1219 return TRUE;
1223 /* Wraps a string in place, replacing a space with a newline character.
1224 * wrapstart is the minimum position to start wrapping or -1 for default */
1225 gboolean utils_wrap_string(gchar *string, gint wrapstart)
1227 gchar *pos, *linestart;
1228 gboolean ret = FALSE;
1230 if (wrapstart < 0)
1231 wrapstart = 80;
1233 for (pos = linestart = string; *pos != '\0'; pos++)
1235 if (pos - linestart >= wrapstart && *pos == ' ')
1237 *pos = '\n';
1238 linestart = pos;
1239 ret = TRUE;
1242 return ret;
1247 * Converts the given UTF-8 encoded string into locale encoding.
1248 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1250 * @param utf8_text UTF-8 encoded text.
1252 * @return The converted string in locale encoding, or a copy of the input string if conversion
1253 * failed. Should be freed with g_free(). If @a utf8_text is @c NULL, @c NULL is returned.
1255 GEANY_API_SYMBOL
1256 gchar *utils_get_locale_from_utf8(const gchar *utf8_text)
1258 #ifdef G_OS_WIN32
1259 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1260 * which would result in wrongly converted strings */
1261 return g_strdup(utf8_text);
1262 #else
1263 gchar *locale_text;
1265 if (! utf8_text)
1266 return NULL;
1267 locale_text = g_locale_from_utf8(utf8_text, -1, NULL, NULL, NULL);
1268 if (locale_text == NULL)
1269 locale_text = g_strdup(utf8_text);
1270 return locale_text;
1271 #endif
1276 * Converts the given string (in locale encoding) into UTF-8 encoding.
1277 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1279 * @param locale_text Text in locale encoding.
1281 * @return The converted string in UTF-8 encoding, or a copy of the input string if conversion
1282 * failed. Should be freed with g_free(). If @a locale_text is @c NULL, @c NULL is returned.
1284 GEANY_API_SYMBOL
1285 gchar *utils_get_utf8_from_locale(const gchar *locale_text)
1287 #ifdef G_OS_WIN32
1288 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1289 * which would result in wrongly converted strings */
1290 return g_strdup(locale_text);
1291 #else
1292 gchar *utf8_text;
1294 if (! locale_text)
1295 return NULL;
1296 utf8_text = g_locale_to_utf8(locale_text, -1, NULL, NULL, NULL);
1297 if (utf8_text == NULL)
1298 utf8_text = g_strdup(locale_text);
1299 return utf8_text;
1300 #endif
1304 /* Pass pointers to free after arg_count.
1305 * The last argument must be NULL as an extra check that arg_count is correct. */
1306 void utils_free_pointers(gsize arg_count, ...)
1308 va_list a;
1309 gsize i;
1310 gpointer ptr;
1312 va_start(a, arg_count);
1313 for (i = 0; i < arg_count; i++)
1315 ptr = va_arg(a, gpointer);
1316 g_free(ptr);
1318 ptr = va_arg(a, gpointer);
1319 if (ptr)
1320 g_warning("Wrong arg_count!");
1321 va_end(a);
1325 /* currently unused */
1326 #if 0
1327 /* Creates a string array deep copy of a series of non-NULL strings.
1328 * The first argument is nothing special.
1329 * The list must be ended with NULL.
1330 * If first is NULL, NULL is returned. */
1331 gchar **utils_strv_new(const gchar *first, ...)
1333 gsize strvlen, i;
1334 va_list args;
1335 gchar *str;
1336 gchar **strv;
1338 g_return_val_if_fail(first != NULL, NULL);
1340 strvlen = 1; /* for first argument */
1342 /* count other arguments */
1343 va_start(args, first);
1344 for (; va_arg(args, gchar*) != NULL; strvlen++);
1345 va_end(args);
1347 strv = g_new(gchar*, strvlen + 1); /* +1 for NULL terminator */
1348 strv[0] = g_strdup(first);
1350 va_start(args, first);
1351 for (i = 1; str = va_arg(args, gchar*), str != NULL; i++)
1353 strv[i] = g_strdup(str);
1355 va_end(args);
1357 strv[i] = NULL;
1358 return strv;
1360 #endif
1364 * Creates a directory if it doesn't already exist.
1365 * Creates intermediate parent directories as needed, too.
1366 * The permissions of the created directory are set 0700.
1368 * @param path The path of the directory to create, in locale encoding.
1369 * @param create_parent_dirs Whether to create intermediate parent directories if necessary.
1371 * @return 0 if the directory was successfully created, otherwise the @c errno of the
1372 * failed operation is returned.
1374 GEANY_API_SYMBOL
1375 gint utils_mkdir(const gchar *path, gboolean create_parent_dirs)
1377 gint mode = 0700;
1378 gint result;
1380 if (path == NULL || strlen(path) == 0)
1381 return EFAULT;
1383 result = (create_parent_dirs) ? g_mkdir_with_parents(path, mode) : g_mkdir(path, mode);
1384 if (result != 0)
1385 return errno;
1386 return 0;
1391 * Gets a list of files from the specified directory.
1392 * Locale encoding is expected for @a path and used for the file list. The list and the data
1393 * in the list should be freed after use, e.g.:
1394 * @code
1395 * g_slist_foreach(list, (GFunc) g_free, NULL);
1396 * g_slist_free(list); @endcode
1398 * @note If you don't need a list you should use the foreach_dir() macro instead -
1399 * it's more efficient.
1401 * @param path The path of the directory to scan, in locale encoding.
1402 * @param full_path Whether to include the full path for each filename in the list. Obviously this
1403 * will use more memory.
1404 * @param sort Whether to sort alphabetically (UTF-8 safe).
1405 * @param error The location for storing a possible error, or @c NULL.
1407 * @return A newly allocated list or @c NULL if no files were found. The list and its data should be
1408 * freed when no longer needed.
1409 * @see utils_get_file_list().
1411 GEANY_API_SYMBOL
1412 GSList *utils_get_file_list_full(const gchar *path, gboolean full_path, gboolean sort, GError **error)
1414 GSList *list = NULL;
1415 GDir *dir;
1416 const gchar *filename;
1418 if (error)
1419 *error = NULL;
1420 g_return_val_if_fail(path != NULL, NULL);
1422 dir = g_dir_open(path, 0, error);
1423 if (dir == NULL)
1424 return NULL;
1426 foreach_dir(filename, dir)
1428 list = g_slist_prepend(list, full_path ?
1429 g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL) : g_strdup(filename));
1431 g_dir_close(dir);
1432 /* sorting last is quicker than on insertion */
1433 if (sort)
1434 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1435 return list;
1440 * Gets a sorted list of files from the specified directory.
1441 * Locale encoding is expected for @a path and used for the file list. The list and the data
1442 * in the list should be freed after use, e.g.:
1443 * @code
1444 * g_slist_foreach(list, (GFunc) g_free, NULL);
1445 * g_slist_free(list); @endcode
1447 * @param path The path of the directory to scan, in locale encoding.
1448 * @param length The location to store the number of non-@c NULL data items in the list,
1449 * unless @c NULL.
1450 * @param error The location for storing a possible error, or @c NULL.
1452 * @return A newly allocated list or @c NULL if no files were found. The list and its data should be
1453 * freed when no longer needed.
1454 * @see utils_get_file_list_full().
1456 GEANY_API_SYMBOL
1457 GSList *utils_get_file_list(const gchar *path, guint *length, GError **error)
1459 GSList *list = utils_get_file_list_full(path, FALSE, TRUE, error);
1461 if (length)
1462 *length = g_slist_length(list);
1463 return list;
1467 /* returns TRUE if any letter in str is a capital, FALSE otherwise. Should be Unicode safe. */
1468 gboolean utils_str_has_upper(const gchar *str)
1470 gunichar c;
1472 if (EMPTY(str) || ! g_utf8_validate(str, -1, NULL))
1473 return FALSE;
1475 while (*str != '\0')
1477 c = g_utf8_get_char(str);
1478 /* check only letters and stop once the first non-capital was found */
1479 if (g_unichar_isalpha(c) && g_unichar_isupper(c))
1480 return TRUE;
1481 /* FIXME don't write a const string */
1482 str = g_utf8_next_char(str);
1484 return FALSE;
1488 /* end can be -1 for haystack->len.
1489 * returns: position of found text or -1. */
1490 gint utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
1492 gint pos;
1494 g_return_val_if_fail(haystack != NULL, -1);
1495 if (haystack->len == 0)
1496 return -1;
1498 g_return_val_if_fail(start >= 0, -1);
1499 if (start >= (gint)haystack->len)
1500 return -1;
1502 g_return_val_if_fail(!EMPTY(needle), -1);
1504 if (end < 0)
1505 end = haystack->len;
1507 pos = utils_strpos(haystack->str + start, needle);
1508 if (pos == -1)
1509 return -1;
1511 pos += start;
1512 if (pos >= end)
1513 return -1;
1514 return pos;
1518 /* Replaces @len characters from offset @a pos.
1519 * len can be -1 to replace the remainder of @a str.
1520 * returns: pos + strlen(replace). */
1521 gint utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
1523 g_string_erase(str, pos, len);
1524 if (replace)
1526 g_string_insert(str, pos, replace);
1527 pos += strlen(replace);
1529 return pos;
1534 * Replaces all occurrences of @a needle in @a haystack with @a replace.
1535 * As of Geany 0.16, @a replace can match @a needle, so the following will work:
1536 * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
1538 * @param haystack The input string to operate on. This string is modified in place.
1539 * @param needle The string which should be replaced.
1540 * @param replace The replacement for @a needle.
1542 * @return Number of replacements made.
1544 GEANY_API_SYMBOL
1545 guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
1547 guint count = 0;
1548 gint pos = 0;
1549 gsize needle_length = strlen(needle);
1551 while (1)
1553 pos = utils_string_find(haystack, pos, -1, needle);
1555 if (pos == -1)
1556 break;
1558 pos = utils_string_replace(haystack, pos, needle_length, replace);
1559 count++;
1561 return count;
1566 * Replaces only the first occurrence of @a needle in @a haystack
1567 * with @a replace.
1568 * For details, see utils_string_replace_all().
1570 * @param haystack The input string to operate on. This string is modified in place.
1571 * @param needle The string which should be replaced.
1572 * @param replace The replacement for @a needle.
1574 * @return Number of replacements made.
1576 * @since 0.16
1578 GEANY_API_SYMBOL
1579 guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace)
1581 gint pos = utils_string_find(haystack, 0, -1, needle);
1583 if (pos == -1)
1584 return 0;
1586 utils_string_replace(haystack, pos, strlen(needle), replace);
1587 return 1;
1591 /* Similar to g_regex_replace but allows matching a subgroup.
1592 * match_num: which match to replace, 0 for whole match.
1593 * literal: FALSE to interpret escape sequences in @a replace.
1594 * returns: number of replacements.
1595 * bug: replaced text can affect matching of ^ or \b */
1596 guint utils_string_regex_replace_all(GString *haystack, GRegex *regex,
1597 guint match_num, const gchar *replace, gboolean literal)
1599 GMatchInfo *minfo;
1600 guint ret = 0;
1601 gint start = 0;
1603 g_assert(literal); /* escapes not implemented yet */
1604 g_return_val_if_fail(replace, 0);
1606 /* ensure haystack->str is not null */
1607 if (haystack->len == 0)
1608 return 0;
1610 /* passing a start position makes G_REGEX_MATCH_NOTBOL automatic */
1611 while (g_regex_match_full(regex, haystack->str, -1, start, 0, &minfo, NULL))
1613 gint end, len;
1615 g_match_info_fetch_pos(minfo, match_num, &start, &end);
1616 len = end - start;
1617 utils_string_replace(haystack, start, len, replace);
1618 ret++;
1620 /* skip past whole match */
1621 g_match_info_fetch_pos(minfo, 0, NULL, &end);
1622 start = end - len + strlen(replace);
1623 g_match_info_free(minfo);
1625 g_match_info_free(minfo);
1626 return ret;
1630 /* Get project or default startup directory (if set), or NULL. */
1631 const gchar *utils_get_default_dir_utf8(void)
1633 if (app->project && !EMPTY(app->project->base_path))
1635 return app->project->base_path;
1638 if (!EMPTY(prefs.default_open_path))
1640 return prefs.default_open_path;
1642 return NULL;
1647 * Wraps @c spawn_sync(), which see.
1649 * @param dir The child's current working directory, or @a NULL to inherit parent's.
1650 * @param argv The child's argument vector.
1651 * @param env The child's environment, or @a NULL to inherit parent's.
1652 * @param flags Ignored.
1653 * @param child_setup Ignored.
1654 * @param user_data Ignored.
1655 * @param std_out The return location for child output, or @a NULL.
1656 * @param std_err The return location for child error messages, or @a NULL.
1657 * @param exit_status The child exit status, as returned by waitpid(), or @a NULL.
1658 * @param error The return location for error or @a NULL.
1660 * @return @c TRUE on success, @c FALSE if an error was set.
1662 GEANY_API_SYMBOL
1663 gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1664 GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out,
1665 gchar **std_err, gint *exit_status, GError **error)
1667 GString *output = std_out ? g_string_new(NULL) : NULL;
1668 GString *errors = std_err ? g_string_new(NULL) : NULL;
1669 gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
1671 if (std_out)
1672 *std_out = g_string_free(output, !result);
1674 if (std_err)
1675 *std_err = g_string_free(errors, !result);
1677 return result;
1682 * Wraps @c spawn_async(), which see.
1684 * @param dir The child's current working directory, or @a NULL to inherit parent's.
1685 * @param argv The child's argument vector.
1686 * @param env The child's environment, or @a NULL to inherit parent's.
1687 * @param flags Ignored.
1688 * @param child_setup Ignored.
1689 * @param user_data Ignored.
1690 * @param child_pid The return location for child process ID, or NULL.
1691 * @param error The return location for error or @a NULL.
1693 * @return @c TRUE on success, @c FALSE if an error was set.
1695 GEANY_API_SYMBOL
1696 gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1697 GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
1698 GError **error)
1700 return spawn_async(dir, NULL, argv, env, child_pid, error);
1704 /* Retrieves the path for the given URI.
1705 * It returns:
1706 * - the path which was determined by g_filename_from_uri() or GIO
1707 * - NULL if the URI is non-local and gvfs-fuse is not installed
1708 * - a new copy of 'uri' if it is not an URI. */
1709 gchar *utils_get_path_from_uri(const gchar *uri)
1711 gchar *locale_filename;
1713 g_return_val_if_fail(uri != NULL, NULL);
1715 if (! utils_is_uri(uri))
1716 return g_strdup(uri);
1718 /* this will work only for 'file://' URIs */
1719 locale_filename = g_filename_from_uri(uri, NULL, NULL);
1720 /* g_filename_from_uri() failed, so we probably have a non-local URI */
1721 if (locale_filename == NULL)
1723 GFile *file = g_file_new_for_uri(uri);
1724 locale_filename = g_file_get_path(file);
1725 g_object_unref(file);
1726 if (locale_filename == NULL)
1728 geany_debug("The URI '%s' could not be resolved to a local path. This means "
1729 "that the URI is invalid or that you don't have gvfs-fuse installed.", uri);
1733 return locale_filename;
1737 gboolean utils_is_uri(const gchar *uri)
1739 g_return_val_if_fail(uri != NULL, FALSE);
1741 return (strstr(uri, "://") != NULL);
1745 /* path should be in locale encoding */
1746 gboolean utils_is_remote_path(const gchar *path)
1748 g_return_val_if_fail(path != NULL, FALSE);
1750 /* if path is an URI and it doesn't start "file://", we take it as remote */
1751 if (utils_is_uri(path) && strncmp(path, "file:", 5) != 0)
1752 return TRUE;
1754 #ifndef G_OS_WIN32
1756 static gchar *fuse_path = NULL;
1757 static gsize len = 0;
1759 if (G_UNLIKELY(fuse_path == NULL))
1761 fuse_path = g_build_filename(g_get_home_dir(), ".gvfs", NULL);
1762 len = strlen(fuse_path);
1764 /* Comparing the file path against a hardcoded path is not the most elegant solution
1765 * but for now it is better than nothing. Ideally, g_file_new_for_path() should create
1766 * proper GFile objects for Fuse paths, but it only does in future GVFS
1767 * versions (gvfs 1.1.1). */
1768 return (strncmp(path, fuse_path, len) == 0);
1770 #endif
1772 return FALSE;
1776 /* Remove all relative and untidy elements from the path of @a filename.
1777 * @param filename must be a valid absolute path.
1778 * @see tm_get_real_path() - also resolves links. */
1779 void utils_tidy_path(gchar *filename)
1781 GString *str;
1782 const gchar *needle;
1783 gboolean preserve_double_backslash = FALSE;
1785 g_return_if_fail(g_path_is_absolute(filename));
1787 str = g_string_new(filename);
1789 if (str->len >= 2 && strncmp(str->str, "\\\\", 2) == 0)
1790 preserve_double_backslash = TRUE;
1792 #ifdef G_OS_WIN32
1793 /* using MSYS we can get Unix-style separators */
1794 utils_string_replace_all(str, "/", G_DIR_SEPARATOR_S);
1795 #endif
1796 /* replace "/./" and "//" */
1797 utils_string_replace_all(str, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1798 utils_string_replace_all(str, G_DIR_SEPARATOR_S G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1800 if (preserve_double_backslash)
1801 g_string_prepend(str, "\\");
1803 /* replace "/../" */
1804 needle = G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S;
1805 while (1)
1807 const gchar *c = strstr(str->str, needle);
1808 if (c == NULL)
1809 break;
1810 else
1812 gssize pos, sub_len;
1814 pos = c - str->str;
1815 if (pos <= 3)
1816 break; /* bad path */
1818 /* replace "/../" */
1819 g_string_erase(str, pos, strlen(needle));
1820 g_string_insert_c(str, pos, G_DIR_SEPARATOR);
1822 /* search for last "/" before found "/../" */
1823 c = g_strrstr_len(str->str, pos, G_DIR_SEPARATOR_S);
1824 sub_len = pos - (c - str->str);
1825 if (! c)
1826 break; /* bad path */
1828 pos = c - str->str; /* position of previous "/" */
1829 g_string_erase(str, pos, sub_len);
1832 if (str->len <= strlen(filename))
1833 memcpy(filename, str->str, str->len + 1);
1834 else
1835 g_warn_if_reached();
1836 g_string_free(str, TRUE);
1841 * Removes characters from a string, in place.
1843 * @param string String to search.
1844 * @param chars Characters to remove.
1846 * @return @a string - return value is only useful when nesting function calls, e.g.:
1847 * @code str = utils_str_remove_chars(g_strdup("f_o_o"), "_"); @endcode
1849 * @see @c g_strdelimit.
1851 GEANY_API_SYMBOL
1852 gchar *utils_str_remove_chars(gchar *string, const gchar *chars)
1854 const gchar *r;
1855 gchar *w = string;
1857 g_return_val_if_fail(string, NULL);
1858 if (G_UNLIKELY(EMPTY(chars)))
1859 return string;
1861 foreach_str(r, string)
1863 if (!strchr(chars, *r))
1864 *w++ = *r;
1866 *w = 0x0;
1867 return string;
1871 /* Gets list of sorted filenames with no path and no duplicates from user and system config */
1872 GSList *utils_get_config_files(const gchar *subdir)
1874 gchar *path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, subdir, NULL);
1875 GSList *list = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1876 GSList *syslist, *node;
1878 if (!list)
1880 utils_mkdir(path, FALSE);
1882 SETPTR(path, g_build_path(G_DIR_SEPARATOR_S, app->datadir, subdir, NULL));
1883 syslist = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1884 /* merge lists */
1885 list = g_slist_concat(list, syslist);
1887 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1888 /* remove duplicates (next to each other after sorting) */
1889 foreach_slist(node, list)
1891 if (node->next && utils_str_equal(node->next->data, node->data))
1893 GSList *old = node->next;
1895 g_free(old->data);
1896 node->next = old->next;
1897 g_slist_free1(old);
1900 g_free(path);
1901 return list;
1905 /* Suffix can be NULL or a string which should be appended to the Help URL like
1906 * an anchor link, e.g. "#some_anchor". */
1907 gchar *utils_get_help_url(const gchar *suffix)
1909 gint skip;
1910 gchar *uri;
1912 #ifdef G_OS_WIN32
1913 skip = 8;
1914 uri = g_strconcat("file:///", app->docdir, "/Manual.html", NULL);
1915 g_strdelimit(uri, "\\", '/'); /* replace '\\' by '/' */
1916 #else
1917 skip = 7;
1918 uri = g_strconcat("file://", app->docdir, "/index.html", NULL);
1919 #endif
1921 if (! g_file_test(uri + skip, G_FILE_TEST_IS_REGULAR))
1922 { /* fall back to online documentation if it is not found on the hard disk */
1923 g_free(uri);
1924 uri = g_strconcat(GEANY_HOMEPAGE, "manual/", VERSION, "/index.html", NULL);
1927 if (suffix != NULL)
1929 SETPTR(uri, g_strconcat(uri, suffix, NULL));
1932 return uri;
1936 static gboolean str_in_array(const gchar **haystack, const gchar *needle)
1938 const gchar **p;
1940 for (p = haystack; *p != NULL; ++p)
1942 if (utils_str_equal(*p, needle))
1943 return TRUE;
1945 return FALSE;
1950 * Copies the current environment into a new array.
1951 * @a exclude_vars is a @c NULL-terminated array of variable names which should be not copied.
1952 * All further arguments are key, value pairs of variables which should be added to
1953 * the environment.
1955 * The argument list must be @c NULL-terminated.
1957 * @param exclude_vars @c NULL-terminated array of variable names to exclude.
1958 * @param first_varname Name of the first variable to copy into the new array.
1959 * @param ... Key-value pairs of variable names and values, @c NULL-terminated.
1961 * @return The new environment array.
1963 GEANY_API_SYMBOL
1964 gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...)
1966 gchar **result;
1967 gchar **p;
1968 gchar **env;
1969 va_list args;
1970 const gchar *key, *value;
1971 guint n, o;
1973 /* count the additional variables */
1974 va_start(args, first_varname);
1975 for (o = 1; va_arg(args, gchar*) != NULL; o++);
1976 va_end(args);
1977 /* the passed arguments should be even (key, value pairs) */
1978 g_return_val_if_fail(o % 2 == 0, NULL);
1980 o /= 2;
1982 /* get all the environ variables */
1983 env = g_listenv();
1985 /* create an array large enough to hold the new environment */
1986 n = g_strv_length(env);
1987 /* 'n + o + 1' could leak a little bit when exclude_vars is set */
1988 result = g_new(gchar *, n + o + 1);
1990 /* copy the environment */
1991 for (n = 0, p = env; *p != NULL; ++p)
1993 /* copy the variable */
1994 value = g_getenv(*p);
1995 if (G_LIKELY(value != NULL))
1997 /* skip excluded variables */
1998 if (exclude_vars != NULL && str_in_array(exclude_vars, *p))
1999 continue;
2001 result[n++] = g_strconcat(*p, "=", value, NULL);
2004 g_strfreev(env);
2006 /* now add additional variables */
2007 va_start(args, first_varname);
2008 key = first_varname;
2009 value = va_arg(args, gchar*);
2010 while (key != NULL)
2012 result[n++] = g_strconcat(key, "=", value, NULL);
2014 key = va_arg(args, gchar*);
2015 if (key == NULL)
2016 break;
2017 value = va_arg(args, gchar*);
2019 va_end(args);
2021 result[n] = NULL;
2023 return result;
2027 /* Joins @a first and @a second into a new string vector, freeing the originals.
2028 * The original contents are reused. */
2029 gchar **utils_strv_join(gchar **first, gchar **second)
2031 gchar **strv;
2032 gchar **rptr, **wptr;
2034 if (!first)
2035 return second;
2036 if (!second)
2037 return first;
2039 strv = g_new0(gchar*, g_strv_length(first) + g_strv_length(second) + 1);
2040 wptr = strv;
2042 foreach_strv(rptr, first)
2043 *wptr++ = *rptr;
2044 foreach_strv(rptr, second)
2045 *wptr++ = *rptr;
2047 g_free(first);
2048 g_free(second);
2049 return strv;
2053 /* Try to parse a date using g_date_set_parse(). It doesn't take any format hint,
2054 * obviously g_date_set_parse() uses some magic.
2055 * The returned GDate object must be freed. */
2056 GDate *utils_parse_date(const gchar *input)
2058 GDate *date = g_date_new();
2060 g_date_set_parse(date, input);
2062 if (g_date_valid(date))
2063 return date;
2065 g_date_free(date);
2066 return NULL;
2070 gchar *utils_parse_and_format_build_date(const gchar *input)
2072 gchar date_buf[255];
2073 GDate *date = utils_parse_date(input);
2075 if (date != NULL)
2077 g_date_strftime(date_buf, sizeof(date_buf), GEANY_TEMPLATES_FORMAT_DATE, date);
2078 g_date_free(date);
2079 return g_strdup(date_buf);
2082 return g_strdup(input);
2086 gchar *utils_get_user_config_dir(void)
2088 #ifdef G_OS_WIN32
2089 return win32_get_user_config_dir();
2090 #else
2091 return g_build_filename(g_get_user_config_dir(), "geany", NULL);
2092 #endif
2096 static gboolean is_osx_bundle(void)
2098 #ifdef MAC_INTEGRATION
2099 gchar *bundle_id = gtkosx_application_get_bundle_id();
2100 if (bundle_id)
2102 g_free(bundle_id);
2103 return TRUE;
2105 #endif
2106 return FALSE;
2110 const gchar *utils_resource_dir(GeanyResourceDirType type)
2112 static const gchar *resdirs[RESOURCE_DIR_COUNT] = {NULL};
2114 if (!resdirs[RESOURCE_DIR_DATA])
2116 #ifdef G_OS_WIN32
2117 gchar *prefix = win32_get_installation_dir();
2119 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "data", NULL);
2120 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2121 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "doc", NULL);
2122 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2123 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", NULL);
2124 g_free(prefix);
2125 #else
2126 if (is_osx_bundle())
2128 # ifdef MAC_INTEGRATION
2129 gchar *prefix = gtkosx_application_get_resource_path();
2131 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "share", "geany", NULL);
2132 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2133 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2134 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2135 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2136 g_free(prefix);
2137 # endif
2139 else
2141 resdirs[RESOURCE_DIR_DATA] = g_build_filename(GEANY_DATADIR, "geany", NULL);
2142 resdirs[RESOURCE_DIR_ICON] = g_build_filename(GEANY_DATADIR, "icons", NULL);
2143 resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL);
2144 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL);
2145 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL);
2147 #endif
2150 return resdirs[type];
2154 void utils_start_new_geany_instance(const gchar *doc_path)
2156 const gchar *const *argv;
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;
2164 if (is_osx_bundle())
2166 const gchar *const osx_argv[] = {exec_path, "-n", "-a", "Geany", doc_path, NULL};
2167 argv = osx_argv;
2169 else
2171 const gchar *const unix_argv[] = {exec_path, "-i", doc_path, NULL};
2172 argv = unix_argv;
2175 if (!utils_spawn_async(NULL, (gchar**) argv, NULL, 0, NULL, NULL, NULL, &err))
2177 g_printerr("Unable to open new window: %s", err->message);
2178 g_error_free(err);
2180 g_free(exec_path);
2182 else
2183 g_printerr("Unable to find 'geany'");