changeset 9175:3e2ea5b69605

[gaim-migrate @ 9970] W and S are now implemented for /cmds in core. This means you can do /me with colors again. This was probably the hardest part of cmds that was left to do. So the rest should be fairly easy. Hopefully there's no major bugs in this. There's some inconsist use of g_utf8_isspace vs strchr(s, ' ') I want to clean up yet that will cause some oddness if you use a tab instead of a space as your argument separater. committer: Tailor Script <tailor@pidgin.im>
author Tim Ringenbach <marv@pidgin.im>
date Sat, 05 Jun 2004 07:33:58 +0000
parents a839ef5d2f34
children 7dc10a14c568
files src/cmds.c src/cmds.h src/gtkconv.c src/util.c src/util.h
diffstat 5 files changed, 216 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/src/cmds.c	Fri Jun 04 04:35:30 2004 +0000
+++ b/src/cmds.c	Sat Jun 05 07:33:58 2004 +0000
@@ -23,6 +23,7 @@
 #include <string.h>
 
 #include "account.h"
+#include "util.h"
 #include "cmds.h"
 
 static GList *cmds = NULL;
@@ -106,7 +107,7 @@
 	}
 }
 
-static gboolean gaim_cmd_parse_args(GaimCmd *cmd, const gchar *s, gchar ***args)
+static gboolean gaim_cmd_parse_args(GaimCmd *cmd, const gchar *s, const gchar *m, gchar ***args)
 {
 	int i;
 	const char *end, *cur;
@@ -127,7 +128,7 @@
 			break;
 		case 'W':
 			if (!(end = strchr(cur, ' '))) end = cur + strlen(cur);
-			(*args)[i] = g_strndup(cur, end - cur); /* XXX apply default formatting here */
+			(*args)[i] = gaim_markup_slice(m, g_utf8_pointer_to_offset(s, cur), g_utf8_pointer_to_offset(s, end));
 			cur += end - cur;
 			break;
 		case 's':
@@ -135,7 +136,7 @@
 			cur = cur + strlen(cur);
 			break;
 		case 'S':
-			(*args)[i] = g_strdup(cur); /* XXX apply default formatting here */
+			(*args)[i] = gaim_markup_slice(m, g_utf8_pointer_to_offset(s, cur), g_utf8_strlen(cur, -1) + 1);
 			cur = cur + strlen(cur);
 			break;
 		}
@@ -156,7 +157,38 @@
 	g_free(args);
 }
 
-GaimCmdStatus gaim_cmd_do_command(GaimConversation *conv, const gchar *cmdline, gchar **error)
+static void gaim_cmd_strip_current_char(gunichar c, char *s, guint len)
+{
+	int bytes;
+
+	bytes = g_unichar_to_utf8(c, NULL);
+	memmove(s, s + bytes, len + 1 - bytes);
+}
+
+static void gaim_cmd_strip_cmd_from_markup(char *markup)
+{
+	guint len = strlen(markup);
+	char *s = markup;
+
+	while (*s) {
+		gunichar c = g_utf8_get_char(s);
+
+		if (c == '<') {
+			s = strchr(s, '>');
+			if (!s)
+				return;
+		} else if (g_unichar_isspace(c)) {
+			gaim_cmd_strip_current_char(c, s, len - (s - markup));
+			return;
+		} else {
+			gaim_cmd_strip_current_char(c, s, len - (s - markup));
+			continue;
+		}
+		s = g_utf8_next_char(s);
+	}
+}
+GaimCmdStatus gaim_cmd_do_command(GaimConversation *conv, const gchar *cmdline,
+                                  const gchar *markup, gchar **error)
 {
 	GaimCmd *c;
 	GList *l;
@@ -165,7 +197,7 @@
 	gboolean found = FALSE, tried_cmd = FALSE, right_type = FALSE, right_prpl = FALSE;
 	const gchar *prpl_id;
 	gchar **args = NULL;
-	gchar *cmd, *rest;
+	gchar *cmd, *rest, *mrest;
 	GaimCmdRet ret = GAIM_CMD_RET_CONTINUE;
 
 	*error = NULL;
@@ -187,6 +219,9 @@
 		rest = "";
 	}
 
+	mrest = g_strdup(markup);
+	gaim_cmd_strip_cmd_from_markup(mrest);
+
 	for (l = cmds; l; l = l->next) {
 		c = l->data;
 
@@ -211,7 +246,7 @@
 		right_prpl = TRUE;
 
 		/* this checks the allow bad args flag for us */
-		if (!gaim_cmd_parse_args(c, rest, &args)) {
+		if (!gaim_cmd_parse_args(c, rest, mrest, &args)) {
 			gaim_cmd_free_args(args);
 			args = NULL;
 			continue;
@@ -235,6 +270,7 @@
 	if (args)
 		gaim_cmd_free_args(args);
 	g_free(cmd);
+	g_free(mrest);
 
 	if (!found)
 		return GAIM_CMD_STATUS_NOT_FOUND;
--- a/src/cmds.h	Fri Jun 04 04:35:30 2004 +0000
+++ b/src/cmds.h	Sat Jun 05 07:33:58 2004 +0000
@@ -97,9 +97,9 @@
  *             This string contains no whitespace, and uses a single character for each argument.
  *             The recognized characters are:
  *               'w' Matches a single word.
- *               'W' Matches a single word, and applies the default formatting to it.
+ *               'W' Matches a single word, with formatting.
  *               's' Matches the rest of the arguments after this point, as a single string.
- *               'S' Same as 's' but applies the default formatting to the matched string.
+ *               'S' Same as 's' but with formatting.
  *             If args is the empty string, then the command accepts no arguments.
  *             The args passed to callback func will be a @c NULL terminated array of null
  *             terminated strings, and will always match the number of arguments asked for,
@@ -136,16 +136,20 @@
  * if aliases are ever implemented.
  *
  * @param conv The conversation the command was typed in.
- * @param cmd The command the user typed (including all arguments) as a single string.
+ * @param cmdline The command the user typed (including all arguments) as a single string.
  *            The caller doesn't have to do any parsing, except removing the command
  *            prefix, which the core has no knowledge of. cmd should not contain the
- *            default formatting, but should contain any user supplied formatting.
+ *            any formatting, and should be in plain text (no html entities).
+ * @param markup This is the same as cmd, but is the formatted version. It should be in
+ *               HTML, with < > and &, at least, escaped to html entities, and should
+ *               include both the default formatting and any extra manual formatting.
  * @param errormsg If the command failed and errormsg is not NULL, it is filled in with
  *                 the appropriate error message. It should be freed by the caller with
  *                 g_free().
  * @return A GaimCmdStatus indicated if the command succeeded or failed.
  */
-GaimCmdStatus gaim_cmd_do_command(GaimConversation *conv, const gchar *cmd, gchar **errormsg);
+GaimCmdStatus gaim_cmd_do_command(GaimConversation *conv, const gchar *cmdline,
+                                  const gchar *markup, gchar **errormsg);
 
 /**
  * List registered commands.
--- a/src/gtkconv.c	Fri Jun 04 04:35:30 2004 +0000
+++ b/src/gtkconv.c	Sat Jun 05 07:33:58 2004 +0000
@@ -349,7 +349,7 @@
 	char *tmp;
 
 	tmp = g_strdup_printf("/me %s", args[0]);
-	
+
 	if (gaim_conversation_get_type(conv) == GAIM_CONV_IM)
 		gaim_conv_im_send(GAIM_CONV_IM(conv), tmp);
 	else if (gaim_conversation_get_type(conv) == GAIM_CONV_CHAT)
@@ -377,11 +377,17 @@
 		cmd = gtk_imhtml_get_text(GTK_IMHTML(gtkconv->entry), NULL, NULL);
 		if(cmd && (strncmp(cmd, prefix, strlen(prefix)) == 0)) {
 			GaimCmdStatus status;
-			char *error, *cmdline;
+			char *error, *cmdline, *markup;
+			GtkTextIter start, end;
 
 			cmdline = cmd + strlen(prefix);
-			status = gaim_cmd_do_command(conv, cmdline, &error);
+			gtk_text_buffer_get_start_iter(GTK_IMHTML(gtkconv->entry)->text_buffer, &start);
+			gtk_text_iter_forward_chars(&start, g_utf8_strlen(prefix, -1));
+			gtk_text_buffer_get_end_iter(GTK_IMHTML(gtkconv->entry)->text_buffer, &end);
+			markup = gtk_imhtml_get_markup_range(GTK_IMHTML(gtkconv->entry), &start, &end);
+			status = gaim_cmd_do_command(conv, cmdline, markup, &error);
 			g_free(cmd);
+			g_free(markup);
 
 			gtk_imhtml_clear(GTK_IMHTML(gtkconv->entry));
 			default_formatize(conv);
--- a/src/util.c	Fri Jun 04 04:35:30 2004 +0000
+++ b/src/util.c	Sat Jun 05 07:33:58 2004 +0000
@@ -1659,6 +1659,127 @@
 
 }
 
+char *
+gaim_markup_slice(const char *str, guint x, guint y)
+{
+	GString *ret;
+	GQueue *q;
+	guint z = 0;
+	gboolean appended = FALSE;
+	gunichar c;
+	char *tag;
+
+	g_return_val_if_fail(x <= y, NULL);
+
+	if (x == y)
+		return g_strdup("");
+
+	ret = g_string_new("");
+	q = g_queue_new();
+
+	while (*str && (z < y)) {
+		c = g_utf8_get_char(str);
+
+		if (c == '<') {
+			char *end = strchr(str, '>');
+
+			if (!end) {
+				g_string_free(ret, TRUE);
+				while ((tag = g_queue_pop_head(q)))
+					g_free(tag);
+				g_queue_free(q);
+				return NULL;
+			}
+
+			if (!g_ascii_strncasecmp(str, "<img ", 5)) {
+				z += strlen("[Image]");
+			} else if (!g_ascii_strncasecmp(str, "<br", 3)) {
+				z += 1;
+			} else if (!g_ascii_strncasecmp(str, "<hr>", 4)) {
+				z += strlen("\n---\n");
+			} else if (!g_ascii_strncasecmp(str, "</", 2)) {
+				/* pop stack */
+				char *tmp;
+
+				tmp = g_queue_pop_head(q);
+				if (tmp)
+					g_free(tmp);
+				/* z += 0; */
+			} else {
+				/* push it unto the stack */
+				char *tmp;
+
+				tmp = g_strndup(str, end - str + 1);
+				g_queue_push_head(q, tmp);
+				/* z += 0; */
+			}
+
+			if (z == x && !appended) {
+				GList *l = q->tail;
+
+				while (l) {
+					tag = l->data;
+					g_string_append(ret, tag);
+					l = l->prev;
+				}
+				appended = TRUE;
+			} else if (z >= x) {
+				g_string_append_len(ret, str, end - str + 1);
+			}
+
+			str = end;
+		} else if (c == '&') {
+			char *end = strchr(str, ';');
+			if (!end) {
+				g_string_free(ret, TRUE);
+				while ((tag = g_queue_pop_head(q)))
+					g_free(tag);
+				g_queue_free(q);
+
+				return NULL;
+			}
+
+			if (z >= x)
+				g_string_append_len(ret, str, end - str + 1);
+
+			z++;
+			str = end;
+		} else {
+			if (z >= x)
+				g_string_append_unichar(ret, c);
+			z++;
+		}
+
+		str = g_utf8_next_char(str);
+	}
+
+	while ((tag = g_queue_pop_head(q))) {
+		char *name;
+
+		name = gaim_markup_get_tag_name(tag);
+		g_string_append_printf(ret, "</%s>", name);
+		g_free(name);
+		g_free(tag);
+	}
+
+	g_queue_free(q);
+	return g_string_free(ret, FALSE);
+}
+
+char *
+gaim_markup_get_tag_name(const char *tag)
+{
+	int i;
+	g_return_val_if_fail(tag != NULL, NULL);
+	g_return_val_if_fail(*tag == '<', NULL);
+
+	for (i = 1; tag[i]; i++)
+		if (tag[i] == '>' || tag[i] == ' ' || tag[i] == '/')
+			break;
+
+	return g_strndup(tag, i);
+}
+
 /**************************************************************************
  * Path/Filename Functions
  **************************************************************************/
--- a/src/util.h	Fri Jun 04 04:35:30 2004 +0000
+++ b/src/util.h	Sat Jun 05 07:33:58 2004 +0000
@@ -223,7 +223,7 @@
  * Extracts a field of data from HTML.
  *
  * This is a scary function. See protocols/msn/msn.c and
- * protocols/yahoo/yahoo.c for example usage.
+ * protocols/yahoo/yahoo_profile.c for example usage.
  *
  * @param str            The string to parse.
  * @param len            The size of str.
@@ -298,6 +298,40 @@
  */
 char *gaim_unescape_html(const char *html);
 
+/**
+ * Returns a newly allocated substring of the HTML UTF-8 string "str".
+ * The markup is preserved such that the substring will have the same
+ * formatting as original string, even though some tags may have been
+ * opened before "x", or may close after "y". All open tags are closed
+ * at the end of the returned string, in the proper order.
+ *
+ * Note that x and y are in character offsets, not byte offsets, and
+ * are offsets into an unformatted version of str. Because of this,
+ * this function may be sensitive to changes in GtkIMHtml and may break
+ * when used with other UI's. libgaim users are encouraged to report and
+ * work out any problems encountered.
+ *
+ * @param str The input NUL terminated, HTML, UTF-8 (or ASCII) string.
+ * @param x The character offset into an unformatted version of str to
+ *          begin at.
+ * @param y The character offset (into an unformatted vesion of str) of
+ *          one past the last character to include in the slice.
+ *
+ * @return The HTML slice of string, with all formatting retained.
+ */
+char *gaim_markup_slice(const char *str, guint x, guint y);
+
+/**
+ * Returns a newly allocated string containing the name of the tag
+ * located at "tag". Tag is expected to point to a '<', and contain
+ * a '>' sometime after that. If there is no '>' and the string is
+ * not NUL terminated, this function can be expected to segfault.
+ *
+ * @param tag The string starting a HTML tag.
+ * @return A string containing the name of the tag.
+ */
+char *gaim_markup_get_tag_name(const char *tag);
+
 /*@}*/