about summary refs log tree commit diff stats
path: root/externaleditor.c
blob: ec56b7e5ff64a867e2654148cca006787cce4d27 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#!/usr/bin/python
# Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Run all the tests inside the test/ directory as a test suite."""
if __name__ == '__main__':
	import unittest
	from test import *
	from sys import exit, argv

	try:
		verbosity = int(argv[1])
	except IndexError:
		verbosity = 2

	tests = []
	for key, val in vars().copy().items():
		if key.startswith('tc_'):
			tests.extend(v for k,v in vars(val).items() if type(v) == type)

	suite = unittest.TestSuite(map(unittest.makeSuite, tests))
	result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
	if len(result.errors) + len(result.failures) > 0:
		exit(1)
id='n353' href='#n353'>353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
/*
 * Copyright (c) 2012 Elias Norberg <xyzzy@kudzu.se>
 * Copyright (c) 2012 Josh Rickmar <jrick@devio.us>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <xombrero.h>

#if WEBKIT_CHECK_VERSION(1, 5, 0)
	/* we got the DOM API we need */

struct edit_src_cb_args {
		WebKitWebFrame		*frame;
		WebKitWebDataSource	*data_src;
};

struct external_editor_args {
		GPid		child_pid;
		char		*path;
		time_t		mtime;
		struct tab	*tab;
		int		(*callback)(const char *,gpointer);
		gpointer	cb_data;
};

int
update_contents(struct external_editor_args *args)
{
	struct stat		st;
	int			fd = -1;
	int			rv, nb;
	GString			*contents = NULL;
	char			buf[XT_EE_BUFSZ];

	rv = stat(args->path, &st);
	if (rv == -1 && errno == ENOENT)
		return (1);
	else if (rv == 0 && st.st_mtime > args->mtime) {
		DPRINTF("File %s has been modified\n", args->path);
		args->mtime = st.st_mtime;

		contents = g_string_sized_new(XT_EE_BUFSZ);
		fd = open(args->path, O_RDONLY);
		if (fd == -1) {
			DPRINTF("open_external_editor_cb, open error, %s\n",
			    strerror(errno));
			goto done;
		}

		for (;;) {
			nb = read(fd, buf, XT_EE_BUFSZ);
			if (nb < 0) {
				g_string_free(contents, TRUE);
				show_oops(args->tab, strerror(errno));
				goto done;
			}

			buf[nb] = '\0';
			contents = g_string_append(contents, buf);

			if (nb < XT_EE_BUFSZ)
				break;
		}
		close(fd);
		fd = -1;

		DPRINTF("external_editor_cb: contents updated\n");
		if (args->callback)
			args->callback(contents->str, args->cb_data);

		g_string_free(contents, TRUE);

		return (0);
	}

done:
	if (fd != -1)
		close(fd);
	return (0);
}

void
external_editor_closed(GPid pid, gint status, gpointer data)
{
	struct external_editor_args	*args;
	struct tab			*t;
	int				found_tab = 0;

	args = (struct external_editor_args *)data;

	TAILQ_FOREACH(t, &tabs, entry)
		if (t == args->tab) {
			found_tab = 1;
			break;
		}

	/* Tab was deleted */
	if (!found_tab)
		goto done;

	/*
	 * unfortunately we can't check the exit status in glib < 2.34,
	 * otherwise a check and warning would be nice here
	 */
	update_contents(args);

done:
	unlink(args->path);
	g_spawn_close_pid(pid);	
}

int
open_external_editor_cb(gpointer data)
{
	struct external_editor_args	*args;
	struct tab			*t;
	int				found_tab = 0;

	args = (struct external_editor_args*)data;

	/* Check if tab is still open */
	TAILQ_FOREACH(t, &tabs, entry)
		if (t == args->tab) {
			found_tab = 1;
			break;
		}

	/* Tab was deleted */
	if (!found_tab)
		goto done;

	if (update_contents(args))
		goto done;

	return (1);
done:
	/* cleanup and remove from event loop */
	g_free(args->path);
	g_free(args->cb_data);
	g_free(args);

	return (0);
}

int
open_external_editor(struct tab *t, const char *contents,
    int (*callback)(const char *, gpointer), gpointer cb_data)
{
	struct stat			st;
	struct external_editor_args	*a;
	GPid				pid;
	char				*cmdstr;
	char				filename[PATH_MAX];
	char				**sv;
	int				fd;
	int				nb, rv;
	int				cnt;

	if (external_editor == NULL)
		return (0);

	snprintf(filename, sizeof filename, "%s" PS "xombreroXXXXXX", temp_dir);

	/* Create a temporary file */
	fd = g_mkstemp(filename);
	if (fd == -1) {
		show_oops(t, "Cannot create temporary file");
		return (1);
	}

	nb = 0;
	while (contents && nb < strlen(contents)) {
		if (strlen(contents) - nb > XT_EE_BUFSZ)
			cnt = XT_EE_BUFSZ;
		else
			cnt = strlen(contents) - nb;

		rv = write(fd, contents+nb, cnt);
		if (rv < 0) {
			close(fd);
			show_oops(t,strerror(errno));
			return (1);
		}

		nb += rv;
	}

	rv = fstat(fd, &st);
	if (rv == -1) {
		close(fd);
		show_oops(t,"Cannot stat file: %s\n", strerror(errno));
		return (1);
	}
	close(fd);

	DPRINTF("edit_src: external_editor: %s\n", external_editor);

	sv = g_strsplit(external_editor, "<file>", -1);
	cmdstr = g_strjoinv(filename, sv);
	g_strfreev(sv);
	sv = g_strsplit_set(cmdstr, " \t", -1);
	if (!g_spawn_async(NULL, sv, NULL,
	    (G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD), NULL, NULL, &pid,
	    NULL)) {
		show_oops(t, "%s: could not spawn process");
		g_strfreev(sv);
		g_free(cmdstr);
		return (1);
	}

	g_strfreev(sv);
	g_free(cmdstr);

	a = g_malloc(sizeof(struct external_editor_args));
	a->child_pid = pid;
	a->path = g_strdup(filename);
	a->tab = t;
	a->mtime = st.st_mtime;
	a->callback = callback;
	a->cb_data = cb_data;

	/* Check every 100 ms if file has changed */
	g_timeout_add(100, (GSourceFunc)open_external_editor_cb,
	    (gpointer)a);

	/* Stop loop  child has terminated */
	g_child_watch_add(pid, external_editor_closed, (gpointer)a);

	return (0);
}

int
edit_src_cb(const char *contents, gpointer data)
{
	struct edit_src_cb_args *args;

	args = (struct edit_src_cb_args *)data;

	webkit_web_frame_load_string(args->frame, contents, NULL,
	    webkit_web_data_source_get_encoding(args->data_src),
	    webkit_web_frame_get_uri(args->frame));
	return (0);
}

int
edit_src(struct tab *t, struct karg *args)
{
	WebKitWebFrame		*frame;
	WebKitWebDataSource	*ds;
	GString			*contents;
	struct edit_src_cb_args	*ext_args;

	if (external_editor == NULL){
		show_oops(t,"Setting external_editor not set");
		return (1);
	}

	frame = webkit_web_view_get_focused_frame(t->wv);
	ds = webkit_web_frame_get_data_source(frame);
	if (webkit_web_data_source_is_loading(ds)) {
		show_oops(t,"Webpage is still loading.");
		return (1);
	}

	contents = webkit_web_data_source_get_data(ds);
	if (!contents)
		show_oops(t,"No contents - opening empty file");

	ext_args = g_malloc(sizeof(struct edit_src_cb_args));
	ext_args->frame = frame;
	ext_args->data_src = ds;

	/* Check every 100 ms if file has changed */
	open_external_editor(t, contents ? contents->str : "", &edit_src_cb,
	    ext_args);
	return (0);
}

struct edit_element_cb_args {
	WebKitDOMElement	*active;
	struct tab		*tab;
};

int
edit_element_cb(const char *contents, gpointer data)
{
	struct				edit_element_cb_args *args;
	WebKitDOMHTMLTextAreaElement	*ta;
	WebKitDOMHTMLInputElement	*el;

	args = (struct edit_element_cb_args*)data;

	if (!args || !args->active)
		return (0);

	el = (WebKitDOMHTMLInputElement*)args->active;
	ta = (WebKitDOMHTMLTextAreaElement*)args->active;

	if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(el))
		webkit_dom_html_input_element_set_value(el, contents);
	else if (WEBKIT_DOM_IS_HTML_TEXT_AREA_ELEMENT(ta))
		webkit_dom_html_text_area_element_set_value(ta, contents);

	return (0);
}

int
edit_element(struct tab *t, struct karg *a)
{
	WebKitDOMHTMLDocument		*doc;
	WebKitDOMElement		*active_element;
	WebKitDOMHTMLTextAreaElement	*ta;
	WebKitDOMHTMLInputElement	*el;
	char				*contents;
	struct edit_element_cb_args	*args;

	if (external_editor == NULL){
		show_oops(t,"Setting external_editor not set");
		return (0);
	}

	doc = (WebKitDOMHTMLDocument*)webkit_web_view_get_dom_document(t->wv);
	active_element = webkit_dom_html_document_get_active_element(doc);
	el = (WebKitDOMHTMLInputElement*)active_element;
	ta = (WebKitDOMHTMLTextAreaElement*)active_element;

	if (doc == NULL || active_element == NULL ||
	    (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(el) == 0 &&
	    WEBKIT_DOM_IS_HTML_TEXT_AREA_ELEMENT(ta) == 0)) {
		show_oops(t, "No active text element!");
		return (1);
	}

	contents = "";
	if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(el))
		contents = webkit_dom_html_input_element_get_value(el);
	else if (WEBKIT_DOM_IS_HTML_TEXT_AREA_ELEMENT(ta))
		contents = webkit_dom_html_text_area_element_get_value(ta);

	if ((args = g_malloc(sizeof(struct edit_element_cb_args))) == NULL)
		return (1);

	args->tab = t;
	args->active = active_element;

	open_external_editor(t, contents, &edit_element_cb,  args);

	return (0);
}

#else /* Just to make things compile. */

int
edit_element(struct tab *t, struct karg *a)
{
	show_oops(t, "external editor feature requires webkit >= 1.5.0");
	return (1);
}

int
edit_src(struct tab *t, struct karg *args)
{
	show_oops(t, "external editor feature requires webkit >= 1.5.0");
	return (1);
}

#endif