medit/moo/mooscript/mooscript-classes-impl.cpp
2010-12-11 00:28:34 -08:00

1558 lines
44 KiB
C++

#include "mooscript-classes.h"
#include "mooscript-classes-util.h"
#include "mooapp/mooapp.h"
#include "mooedit/moofileenc.h"
#include "mooutils/mooutils-misc.h"
#include "mooutils/moodialogs.h"
#include <string.h>
#include <sstream>
namespace mom {
// static void check_no_args(const VariantArray &args)
// {
// if (args.size() != 0)
// Error::raise("no arguments expected");
// }
//
// static void check_1_arg(const VariantArray &args)
// {
// if (args.size() != 1)
// Error::raise("exactly one argument expected");
// }
//
// template<typename T>
// static moo::SharedPtr<T> get_object(const Variant &val, bool null_ok = false)
// {
// if (null_ok && val.vt() == VtVoid)
// return moo::SharedPtr<T>();
//
// if (val.vt() != VtObject)
// Error::raise("object expected");
//
// HObject h = val.value<VtObject>();
// if (null_ok && h.id() == 0)
// return moo::SharedPtr<T>();
//
// moo::SharedPtr<Object> obj = Object::lookup_object(h);
//
// if (!obj)
// Error::raise("bad object");
// if (&obj->meta() != &T::class_meta())
// Error::raise("bad object");
//
// return moo::SharedPtr<T>(static_cast<T*>(obj.get()));
// }
//
// static bool get_bool(const Variant &val)
// {
// if (val.vt() != VtBool)
// Error::raise("boolean expected");
// return val.value<VtBool>();
// }
static String get_string(const Variant &val, bool null_ok = false)
{
if (null_ok && val.vt() == VtVoid)
return String();
if (val.vt() != VtString)
Error::raise("string expected");
return val.value<VtString>();
}
static moo::Vector<String> get_string_list(const Variant &val)
{
VariantArray ar;
if (val.vt() == VtArray)
ar = val.value<VtArray>();
else if (val.vt() == VtArgList)
ar = val.value<VtArgList>();
else
Error::raise("list expected");
moo::Vector<String> ret;
for (int i = 0, c = ar.size(); i < c; ++i)
ret.append(get_string(ar[i]));
return ret;
}
template<typename GClass, typename Class>
static VariantArray wrap_gslist(GSList *list)
{
VariantArray array;
while (list)
{
GClass *gobj = (GClass*) list->data;
array.append(HObject(gobj ? Class::wrap(gobj)->id() : 0));
list = list->next;
}
return array;
}
template<typename GArrayClass, typename GClass, typename Class>
static VariantArray wrap_array(GArrayClass *ar)
{
VariantArray array;
for (guint i = 0; i < ar->n_elms; ++i)
{
GClass *gobj = ar->elms[i];
array.append(HObject(gobj ? Class::wrap(gobj)->id() : 0));
}
return array;
}
static Index get_index(const Variant &val)
{
switch (val.vt())
{
case VtIndex:
return val.value<VtIndex>();
case VtInt:
return Index(val.value<VtInt>());
case VtBase1:
return val.value<VtBase1>().get_index();
default:
Error::raise("index expected");
}
}
static void get_iter(gint64 pos, GtkTextBuffer *buf, GtkTextIter *iter)
{
if (pos > gtk_text_buffer_get_char_count(buf) || pos < 0)
Error::raise("invalid offset");
gtk_text_buffer_get_iter_at_offset(buf, iter, pos);
}
static void get_iter(const Variant &val, GtkTextBuffer *buf, GtkTextIter *iter)
{
get_iter(get_index(val).get(), buf, iter);
}
static void get_pair(const Variant &val, Variant &elm1, Variant &elm2)
{
VariantArray ar;
if (val.vt() == VtArray)
ar = val.value<VtArray>();
else if (val.vt() == VtArgList)
ar = val.value<VtArgList>();
else
Error::raise("pair of values expected");
if (ar.size() != 2)
Error::raise("pair of values expected");
elm1 = ar[0];
elm2 = ar[1];
}
static VariantArray make_pair(const Variant &elm1, const Variant &elm2)
{
VariantArray ar;
ar.append(elm1);
ar.append(elm2);
return ar;
}
static void get_iter_pair(const Variant &val, GtkTextBuffer *buf, GtkTextIter *iter1, GtkTextIter *iter2)
{
Variant elm1, elm2;
get_pair(val, elm1, elm2);
get_iter(elm1, buf, iter1);
get_iter(elm2, buf, iter2);
}
///////////////////////////////////////////////////////////////////////////////
//
// Application
//
///
/// @node Application object
/// @section Application object
/// @helpsection{SCRIPT_APPLICATION}
/// @table @method
///
/// @item Application.editor()
/// returns Editor object.
Editor *Application::editor()
{
return &Editor::get_instance();
}
/// @item Application.quit()
/// quit @medit{}.
void Application::quit()
{
moo_app_quit(moo_app_get_instance());
}
struct DialogButtons
{
GtkButtonsType bt;
moo::Vector<String> custom;
String default_button;
};
static DialogButtons parse_buttons(const Variant &var)
{
DialogButtons buttons = { GTK_BUTTONS_NONE };
if (var.vt() == VtVoid)
return buttons;
if (var.vt() == VtArray)
{
buttons.custom = get_string_list(var);
return buttons;
}
if (var.vt() == VtString)
{
const String &str = var.value<VtString>();
if (str == "none")
buttons.bt = GTK_BUTTONS_NONE;
else if (str == "ok")
buttons.bt = GTK_BUTTONS_OK;
else if (str == "close")
buttons.bt = GTK_BUTTONS_CLOSE;
else if (str == "cancel")
buttons.bt = GTK_BUTTONS_CANCEL;
else if (str == "yesno")
buttons.bt = GTK_BUTTONS_YES_NO;
else if (str == "okcancel")
buttons.bt = GTK_BUTTONS_OK_CANCEL;
else
Error::raisef("in function %s, invalid dialog buttons value '%s'",
(const char*) current_func().name, (const char*) str);
return buttons;
}
Error::raisef("in function %s, invalid dialog buttons value",
(const char*) current_func().name);
}
static void
add_buttons (GtkWidget *dialog, const DialogButtons &buttons)
{
int default_response = G_MININT;
for (int i = 0, c = buttons.custom.size(); i < c; ++i)
{
gtk_dialog_add_button(GTK_DIALOG(dialog), buttons.custom[i], i);
if (buttons.custom[i] == buttons.default_button)
default_response = i;
}
if (default_response < 0 && !buttons.default_button.empty())
{
if (buttons.default_button == "ok")
default_response = GTK_RESPONSE_OK;
else if (buttons.default_button == "close")
default_response = GTK_RESPONSE_CLOSE;
else if (buttons.default_button == "cancel")
default_response = GTK_RESPONSE_CANCEL;
else if (buttons.default_button == "yes")
default_response = GTK_RESPONSE_YES;
else if (buttons.default_button == "no")
default_response = GTK_RESPONSE_NO;
else
Error::raisef("in function %s, invalid default button '%s'",
(const char*) current_func().name,
(const char*) buttons.default_button);
}
if (default_response != G_MININT)
gtk_dialog_set_default_response(GTK_DIALOG(dialog), default_response);
}
struct DialogOptions
{
String title;
String dialog_id;
String icon;
DialogButtons buttons;
gint64 width;
gint64 height;
DocumentWindow *parent;
};
struct FileDialogOptions
{
String filename;
bool multiple;
bool directory;
bool save;
};
struct ListDialogOptions
{
moo::Vector<String> columns;
moo::Vector<String> data;
bool checklist;
bool radiolist;
bool editable;
bool show_header;
String return_column;
};
struct MessageDialogOptions
{
String kind;
String text;
String secondary_text;
};
struct EntryDialogOptions
{
String text;
String entry_text;
bool hide;
};
struct TextDialogOptions
{
String text;
String info_text;
String filename;
bool editable;
};
static void setup_dialog(GtkWidget *dialog, const DialogOptions &opts)
{
add_buttons(dialog, opts.buttons);
if (!opts.title.empty())
gtk_window_set_title(GTK_WINDOW(dialog), opts.title);
if (opts.parent)
moo_window_set_parent(dialog, GTK_WIDGET(opts.parent->gobj()));
if (!opts.dialog_id.empty())
{
String prefs_key("Dialogs/");
prefs_key += opts.dialog_id;
_moo_window_set_remember_size(GTK_WINDOW(dialog), prefs_key, opts.width, opts.height, FALSE);
}
else if (opts.width > 0 && opts.height > 0)
{
gtk_window_set_default_size(GTK_WINDOW(dialog), opts.width, opts.height);
}
if (!opts.icon.empty())
gtk_window_set_icon_name(GTK_WINDOW(dialog), opts.icon);
}
static String response_to_string(int response, const DialogOptions &opts)
{
if (response >= 0)
{
if (response >= opts.buttons.custom.size())
Error::raisef("in function %s, got unexpected response %d from dialog",
(const char*) current_func().name, response);
return opts.buttons.custom[response];
}
switch (response)
{
case GTK_RESPONSE_NONE:
case GTK_RESPONSE_DELETE_EVENT:
return "";
case GTK_RESPONSE_REJECT:
return "reject";
case GTK_RESPONSE_ACCEPT:
return "accept";
case GTK_RESPONSE_OK:
return "ok";
case GTK_RESPONSE_CANCEL:
return "cancel";
case GTK_RESPONSE_CLOSE:
return "close";
case GTK_RESPONSE_YES:
return "yes";
case GTK_RESPONSE_NO:
return "no";
case GTK_RESPONSE_APPLY:
return "apply";
case GTK_RESPONSE_HELP:
return "help";
default:
Error::raisef("in function %s, got unexpected response %d from dialog",
(const char*) current_func().name, response);
}
}
static Variant show_file_dialog(G_GNUC_UNUSED const FileDialogOptions &dopts, G_GNUC_UNUSED const DialogOptions &opts)
{
Error::raise("not implemented");
}
static Variant show_list_dialog(G_GNUC_UNUSED const ListDialogOptions &dopts, G_GNUC_UNUSED const DialogOptions &opts)
{
Error::raise("not implemented");
}
static Variant show_message_dialog(const MessageDialogOptions &dopts, const DialogOptions &opts)
{
GtkMessageType type = GTK_MESSAGE_OTHER;
if (dopts.kind == "error")
type = GTK_MESSAGE_ERROR;
else if (dopts.kind == "warning")
type = GTK_MESSAGE_WARNING;
else if (dopts.kind == "question")
type = GTK_MESSAGE_QUESTION;
else if (dopts.kind == "information" || dopts.kind == "info")
type = GTK_MESSAGE_INFO;
else
Error::raisef("in function %s, invalid message dialog kind '%s'",
(const char*) current_func().name, (const char*) dopts.kind);
GtkWidget *dialog = gtk_message_dialog_new (0, GTK_DIALOG_MODAL, type, opts.buttons.bt,
"%s", (const char*) dopts.text);
if (!dopts.secondary_text.empty())
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog), "%s",
(const char*) dopts.secondary_text);
setup_dialog(dialog, opts);
int response = gtk_dialog_run(GTK_DIALOG(dialog));
return response_to_string(response, opts);
}
static Variant show_entry_dialog(G_GNUC_UNUSED const EntryDialogOptions &dopts, G_GNUC_UNUSED const DialogOptions &opts)
{
Error::raise("not implemented");
}
static Variant show_text_dialog(G_GNUC_UNUSED const TextDialogOptions &dopts, G_GNUC_UNUSED const DialogOptions &opts)
{
Error::raise("not implemented");
}
/// @item Application.dialog()
/// show a dialog.
Variant Application::dialog(const ArgSet &args)
{
if (!args.pos.empty())
Error::raisef("in function %s, no positional arguments expected",
(const char*) current_func().name);
DialogOptions opts;
opts.title = get_kwarg_string_opt(args, "title");
opts.dialog_id = get_kwarg_string_opt(args, "id");
opts.icon = get_kwarg_string_opt(args, "icon");
opts.width = get_kwarg_int_opt(args, "width", -1);
opts.height = get_kwarg_int_opt(args, "height", -1);
opts.buttons = parse_buttons(get_kwarg_variant_opt(args, "buttons"));
opts.buttons.default_button = get_kwarg_string_opt(args, "default_button");
opts.parent = get_object_arg_opt<DocumentWindow>(args.kw.value("parent"), "parent");
String kind = get_kwarg_string(args, "kind");
if (kind == "file" || kind == "file-selection")
{
FileDialogOptions dopts;
dopts.filename = get_kwarg_string_opt(args, "filename");
dopts.multiple = get_kwarg_bool_opt(args, "multiple");
dopts.directory = get_kwarg_bool_opt(args, "directory");
dopts.save = get_kwarg_bool_opt(args, "save");
return show_file_dialog(dopts, opts);
}
if (kind == "list")
{
ListDialogOptions dopts;
dopts.columns = get_string_list(args.kw.value("column"));
dopts.data = get_string_list(args.kw.value("data"));
dopts.checklist = get_kwarg_bool_opt(args, "checklist");
dopts.radiolist = get_kwarg_bool_opt(args, "radiolist");
dopts.editable = get_kwarg_bool_opt(args, "editable");
dopts.show_header = get_kwarg_bool_opt(args, "show_header");
dopts.return_column = get_kwarg_string_opt(args, "return_column");
return show_list_dialog(dopts, opts);
}
if (kind == "error" || kind == "warning" || kind == "question" || kind == "information")
{
MessageDialogOptions dopts;
dopts.kind = kind;
dopts.text = get_kwarg_string(args, "text");
dopts.secondary_text = get_kwarg_string_opt(args, "secondary_text");
return show_message_dialog(dopts, opts);
}
if (kind == "entry" || kind == "text-entry")
{
EntryDialogOptions dopts;
dopts.text = get_kwarg_string_opt(args, "text");
dopts.entry_text = get_kwarg_string_opt(args, "entry_text");
dopts.hide = get_kwarg_bool_opt(args, "hide");
return show_entry_dialog(dopts, opts);
}
if (kind == "text" || kind == "text-info")
{
TextDialogOptions dopts;
dopts.text = get_kwarg_string_opt(args, "text");
dopts.info_text = get_kwarg_string_opt(args, "info_text");
dopts.filename = get_kwarg_string_opt(args, "filename");
dopts.editable = get_kwarg_bool_opt(args, "editable");
return show_text_dialog(dopts, opts);
}
Error::raisef("in function %s, invalid dialog kind '%s'",
(const char*) current_func().name, (const char*) kind);
}
///
/// @end table
///
///////////////////////////////////////////////////////////////////////////////
//
// Editor
//
///
/// @node Editor object
/// @section Editor object
/// @helpsection{SCRIPT_EDITOR}
/// @table @method
///
/// @item Editor.active_document()
/// returns current active document or @null{} if there are no open documents
Document *Editor::active_document()
{
return Document::wrap(moo_editor_get_active_doc(moo_editor_instance()));
}
/// @item Editor.set_active_document(doc)
/// makes @param{doc} active
void Editor::set_active_document(Document &doc)
{
moo_editor_set_active_doc(moo_editor_instance(), doc.gobj());
}
/// @item Editor.active_window()
/// returns current active window
DocumentWindow *Editor::active_window()
{
return DocumentWindow::wrap(moo_editor_get_active_window(moo_editor_instance()));
}
/// @item Editor.set_active_window(window)
/// makes @param{window} active
void Editor::set_active_window(DocumentWindow &window)
{
moo_editor_set_active_window(moo_editor_instance(), window.gobj());
}
/// @item Editor.documents()
/// returns list of all open documents
VariantArray Editor::documents()
{
MooEditArray *docs = moo_editor_get_docs(moo_editor_instance());
VariantArray ret = wrap_array<MooEditArray, MooEdit, Document>(docs);
moo_edit_array_free(docs);
return ret;
}
/// @item Editor.windows()
/// returns list of all document windows
VariantArray Editor::windows()
{
MooEditWindowArray *windows = moo_editor_get_windows(moo_editor_instance());
VariantArray ret = wrap_array<MooEditWindowArray, MooEditWindow, DocumentWindow>(windows);
moo_edit_window_array_free(windows);
return ret;
}
/// @item Editor.new_document(window=null)
/// creates new document in specified window. If window is @null{} then
/// the document is created in an existing window
Document *Editor::new_document(DocumentWindow *window)
{
return Document::wrap(moo_editor_new_doc(moo_editor_instance(), window ? window->gobj() : NULL));
}
/// @item Editor.new_window()
/// creates new document window
DocumentWindow *Editor::new_window()
{
return DocumentWindow::wrap(moo_editor_new_window(moo_editor_instance()));
}
/// @item Editor.get_document_by_path(path)
/// returns document with path @param{path} or @null{}.
Document *Editor::get_document_by_path(const String &path)
{
return Document::wrap(moo_editor_get_doc_for_path(moo_editor_instance(), path));
}
/// @item Editor.get_document_by_uri(path)
/// returns document with uri @param{uri} or @null{}.
Document *Editor::get_document_by_uri(const String &uri)
{
return Document::wrap(moo_editor_get_doc_for_uri(moo_editor_instance(), uri));
}
static MooFileEncArray *get_file_info_list(const Variant &val, bool uri)
{
moo::Vector<String> filenames = get_string_list(val);
if (filenames.empty())
return NULL;
MooFileEncArray *files = moo_file_enc_array_new();
for (int i = 0, c = filenames.size(); i < c; ++i)
{
MooFileEnc *fenc = uri ?
moo_file_enc_new_for_uri(filenames[i], NULL) :
moo_file_enc_new_for_path(filenames[i], NULL);
if (!fenc)
goto error;
moo_file_enc_array_take(files, fenc);
}
return files;
error:
moo_file_enc_array_free(files);
Error::raise("error");
}
static void open_files_or_uris(const VariantArray &file_array, DocumentWindow *window, bool uri)
{
MooFileEncArray *files = get_file_info_list(file_array, uri);
moo_editor_open(moo_editor_instance(), window ? window->gobj() : NULL, NULL, files);
moo_file_enc_array_free(files);
}
/// @item Editor.open_files(files, window=null)
/// open files. If @param{window} is given then open files in that window,
/// otherwise in an existing window.
void Editor::open_files(const VariantArray &files, DocumentWindow *window)
{
open_files_or_uris(files, window, false);
}
/// @item Editor.open_uris(uris, window=null)
/// open files. If @param{window} is given then open files in that window,
/// otherwise in an existing window.
void Editor::open_uris(const VariantArray &uris, DocumentWindow *window)
{
open_files_or_uris(uris, window, true);
}
static Document *open_file_or_uri(const String &file, const String &encoding, DocumentWindow *window, bool uri, bool new_file)
{
MooEdit *doc = NULL;
if (new_file)
{
if (uri)
{
moo_assert_not_reached();
Error::raise("error");
}
else
{
doc = moo_editor_new_file(moo_editor_instance(), window ? window->gobj() : NULL, NULL, file, encoding);
}
}
else
{
if (uri)
doc = moo_editor_open_uri(moo_editor_instance(), window ? window->gobj() : NULL, NULL, file, encoding);
else
doc = moo_editor_open_file(moo_editor_instance(), window ? window->gobj() : NULL, NULL, file, encoding);
}
return Document::wrap(doc);
}
/// @item Editor.open_file(file, encoding=null, window=null)
/// open file. If @param{encoding} is @null{} or "auto" then pick character
/// encoding automatically, otherwise use @param{encoding}. If @param{window}
/// is given then open files in that window, otherwise in an existing window.
Document *Editor::open_file(const String &file, const String &encoding, DocumentWindow *window)
{
return open_file_or_uri(file, encoding, window, false, false);
}
/// @item Editor.open_uri(uri, encoding=null, window=null)
/// open file. If @param{encoding} is @null{} or "auto" then pick character
/// encoding automatically, otherwise use @param{encoding}. If @param{window}
/// is given then open files in that window, otherwise in an existing window.
Document *Editor::open_uri(const String &uri, const String &encoding, DocumentWindow *window)
{
return open_file_or_uri(uri, encoding, window, true, false);
}
/// @item Editor.new_file(file, encoding=null, window=null)
/// open file if it exists on disk or create a new one. If @param{encoding} is
/// @null{} or "auto" then pick character encoding automatically, otherwise use
/// @param{encoding}. If @param{window} is given then open file in that window,
/// otherwise in an existing window.
Document *Editor::new_file(const String &file, const String &encoding, DocumentWindow *window)
{
return open_file_or_uri(file, encoding, window, false, true);
}
/// @item Editor.reload(doc)
/// reload document.
void Editor::reload(Document &doc)
{
moo_edit_reload(doc.gobj(), NULL, NULL);
}
/// @item Editor.save(doc)
/// save document.
bool Editor::save(Document &doc)
{
return moo_edit_save(doc.gobj(), NULL);
}
/// @item Editor.save_as(doc, filename=null)
/// save document as @param{filename}. If @param{filename} is not given then
/// first ask user for new filename.
bool Editor::save_as(Document &doc, const String &filename)
{
return moo_edit_save_as(doc.gobj(),
filename.empty() ? NULL : (const char*) filename,
NULL,
NULL);
}
/// @item Editor.close(doc)
/// close document.
bool Editor::close(Document &doc)
{
return moo_edit_close(doc.gobj(), TRUE);
}
/// @item Editor.close_documents(docs)
/// close all documents in the list @param{docs}.
bool Editor::close_documents(const VariantArray &arr)
{
moo::Vector<MooEdit*> pvec;
for (int i = 0, c = arr.size(); i < c; ++i)
pvec.append(get_object_arg<Document>(arr[i], "doc").gobj());
MooEditArray *docs = moo_edit_array_new ();
for (int i = 0, c = pvec.size(); i < c; ++i)
moo_edit_array_append (docs, pvec[i]);
bool retval = moo_editor_close_docs (moo_editor_instance(), docs, TRUE);
moo_edit_array_free (docs);
return retval;
}
/// @item Editor.close_window(window)
/// close window.
bool Editor::close_window(DocumentWindow &window)
{
return moo_editor_close_window(moo_editor_instance(), window.gobj(), TRUE);
}
///
/// @end table
///
///////////////////////////////////////////////////////////////////////////////
//
// DocumentWindow
//
///
/// @node DocumentWindow object
/// @section DocumentWindow object
/// @helpsection{SCRIPT_DOCUMENT_WINDOW}
/// @table @method
///
/// @item DocumentWindow.editor()
/// returns Editor object.
Editor *DocumentWindow::editor()
{
return &Editor::get_instance();
}
/// @item DocumentWindow.active_document()
/// returns current active document in this window.
Document *DocumentWindow::active_document()
{
return Document::wrap(moo_edit_window_get_active_doc(gobj()));
}
/// @item DocumentWindow.set_active_document(doc)
/// makes active document @param{doc}.
void DocumentWindow::set_active_document(Document &doc)
{
moo_edit_window_set_active_doc(gobj(), doc.gobj());
}
/// @item DocumentWindow.documents()
/// returns list of all documents in this window.
VariantArray DocumentWindow::documents()
{
MooEditArray *docs = moo_edit_window_get_docs(gobj());
VariantArray ret = wrap_array<MooEditArray, MooEdit, Document>(docs);
moo_edit_array_free(docs);
return ret;
}
/// @item DocumentWindow.is_active()
/// returns whether this window is the active one.
bool DocumentWindow::is_active()
{
return gobj() == moo_editor_get_active_window(moo_editor_instance());
}
/// @item DocumentWindow.set_active()
/// makes this window active.
void DocumentWindow::set_active()
{
moo_editor_set_active_window(moo_editor_instance(), gobj());
}
///
/// @end table
///
///////////////////////////////////////////////////////////////////////////////
//
// Document
//
///
/// @node Document object
/// @section Document object
/// @helpsection{DOCUMENT}
///
/// @table @method
static GtkTextBuffer *buffer(Document *doc)
{
return gtk_text_view_get_buffer(GTK_TEXT_VIEW(doc->gobj()));
}
/// @item Document.filename()
/// returns full file path of the document or @null{} if the document
/// has not been saved yet or if it can't be represented with a local
/// path (e.g. if it is in a remote location like a web site).
/// @itemize @minus
/// @item Untitled => @null{}
/// @item @file{/home/user/example.txt} => @code{"/home/user/example.txt"}
/// @item @file{http://example.com/index.html} => @null{}
/// @end itemize
String Document::filename()
{
char *filename = moo_edit_get_utf8_filename(gobj());
return filename ? String::take_utf8(filename) : String::Null();
}
/// @item Document.uri()
/// returns URI of the document or @null{} if the document has not been
/// saved yet.
String Document::uri()
{
char *uri = moo_edit_get_uri(gobj());
return uri ? String::take_utf8(uri) : String::Null();
}
/// @item Document.basename()
/// returns basename of the document, that is the full path minus directory
/// part. If the document has not been saved yet, then it returns the name
/// shown in the titlebar, e.g. "Untitled".
String Document::basename()
{
return String(moo_edit_get_display_basename(gobj()));
}
/// @item Document.is_modified()
/// returns whether the document is modified.
bool Document::is_modified()
{
return MOO_EDIT_IS_MODIFIED(gobj());
}
/// @item Document.set_modified(modified)
/// sets modification state of the document.
void Document::set_modified(bool modified)
{
moo_edit_set_modified(gobj(), modified);
}
/// @item Document.encoding()
/// returns character encoding of the document.
String Document::encoding()
{
return moo_edit_get_encoding(gobj());
}
/// @item Document.set_encoding(encoding)
/// set character encoding of the document, it will be used when the document
/// is saved.
void Document::set_encoding(const String &enc)
{
moo_edit_set_encoding(gobj(), enc);
}
String Document::line_endings()
{
switch (moo_edit_get_line_end_type(gobj()))
{
case MOO_LE_UNIX:
return "unix";
case MOO_LE_WIN32:
return "win32";
case MOO_LE_MAC:
return "mac";
case MOO_LE_MIX:
return "mix";
case MOO_LE_NONE:
return "none";
}
moo_assert_not_reached();
Error::raise("error");
}
void Document::set_line_endings(const String &str_le)
{
MooLineEndType le = MOO_LE_NONE;
if (str_le == "unix")
le = MOO_LE_UNIX;
else if (str_le == "win32")
le = MOO_LE_WIN32;
else if (str_le == "mac")
le = MOO_LE_MAC;
else if (str_le == "mix")
le = MOO_LE_MIX;
else if (str_le == "none")
le = MOO_LE_NONE;
else
Error::raise("invalid line ending type");
moo_edit_set_line_end_type(gobj(), le);
}
/// @item Document.reload()
/// reload the document.
void Document::reload()
{
Editor::get_instance().reload(*this);
}
/// @item Document.save()
/// save the document.
bool Document::save()
{
return Editor::get_instance().save(*this);
}
/// @item Document.save_as(filename=null)
/// save the document as @param{filename}. If @param{filename} is @null{} then
/// @uilabel{Save As} will be shown to choose new filename.
bool Document::save_as(const String &filename)
{
return Editor::get_instance().save_as(*this, filename);
}
/// @item Document.can_undo()
/// returns whether undo action is available.
bool Document::can_undo()
{
return moo_text_view_can_undo(MOO_TEXT_VIEW(gobj()));
}
/// @item Document.can_redo()
/// returns whether redo action is available.
bool Document::can_redo()
{
return moo_text_view_can_redo(MOO_TEXT_VIEW(gobj()));
}
/// @item Document.undo()
/// undo.
void Document::undo()
{
moo_text_view_undo(MOO_TEXT_VIEW(gobj()));
}
/// @item Document.redo()
/// redo.
void Document::redo()
{
moo_text_view_redo(MOO_TEXT_VIEW(gobj()));
}
/// @item Document.begin_not_undoable_action()
/// mark the beginning of a non-undoable operation. Undo stack will be erased
/// and undo will not be recorded until @method{end_not_undoable_action()} call.
void Document::begin_not_undoable_action()
{
moo_text_view_begin_not_undoable_action(MOO_TEXT_VIEW(gobj()));
}
/// @item Document.end_not_undoable_action()
/// end the non-undoable operation started with @method{begin_not_undoable_action()}.
void Document::end_not_undoable_action()
{
moo_text_view_end_not_undoable_action(MOO_TEXT_VIEW(gobj()));
}
/// @end table
/// @table @method
/// @item Document.start_pos()
/// position at the beginning of the document (0 in Python, 1 in Lua, etc.)
gint64 Document::start_pos()
{
return 0;
}
/// @item Document.end_pos()
/// position at the end of the document. This is the position past the last
/// character: it points to no character, but it is a valid position for
/// text insertion, cursor may be put there, etc.
gint64 Document::end_pos()
{
return gtk_text_buffer_get_char_count(buffer(this));
}
/// @item Document.cursor_pos()
/// position at the cursor.
gint64 Document::cursor_pos()
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter iter;
gtk_text_buffer_get_iter_at_mark(buf, &iter, gtk_text_buffer_get_insert(buf));
return gtk_text_iter_get_offset(&iter);
}
/// @item Document.set_cursor_pos(pos)
/// move cursor to position @param{pos}.
void Document::set_cursor_pos(gint64 pos)
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter iter;
get_iter(pos, buf, &iter);
gtk_text_buffer_place_cursor(buf, &iter);
}
/// @item Document.selection()
/// returns selection bounds as a list of two items, start and end. Returned
/// list is always sorted, use @method{cursor()} and @method{selection_bound()}
/// if you need to distinguish beginning and end of selection. If no text is
/// is selected, then it returns pair @code{[cursor, cursor]}.
VariantArray Document::selection()
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
gtk_text_buffer_get_selection_bounds(buf, &start, &end);
return make_pair(Index(gtk_text_iter_get_offset(&start)),
Index(gtk_text_iter_get_offset(&end)));
}
/// @item Document.set_selection(bounds_as_list)
/// @item Document.set_selection(start, end)
/// select text.
void Document::set_selection(const ArgList &args)
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
if (args.size() == 1)
{
get_iter_pair(args[0], buf, &start, &end);
}
else if (args.size() == 2)
{
get_iter(args[0], buf, &start);
get_iter(args[1], buf, &end);
}
else
{
Error::raise("exactly one or two arguments expected");
}
gtk_text_buffer_select_range(buf, &start, &end);
}
/// @item Document.selection_bound()
/// returns the selection bound other than cursor position. Selection is
/// either [cursor, selection_bound) or [selection_bound, cursor), depending
/// on direction user dragged the mouse (or on @method{set_selection}
/// arguments).
gint64 Document::selection_bound()
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter iter;
gtk_text_buffer_get_iter_at_mark(buf, &iter, gtk_text_buffer_get_selection_bound(buf));
return gtk_text_iter_get_offset(&iter);
}
/// @item Document.has_selection()
/// whether any text is selected.
bool Document::has_selection()
{
return gtk_text_buffer_get_selection_bounds(buffer(this), NULL, NULL);
}
/// @item Document.char_count()
/// character count.
gint64 Document::char_count()
{
return gtk_text_buffer_get_char_count(buffer(this));
}
/// @item Document.line_count()
/// line count.
gint64 Document::line_count()
{
return gtk_text_buffer_get_line_count(buffer(this));
}
/// @item Document.line_at_pos(pos)
/// returns index of the line which contains position @param{pos}.
gint64 Document::line_at_pos(gint64 pos)
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter iter;
get_iter(pos, buf, &iter);
return gtk_text_iter_get_line(&iter);
}
/// @item Document.pos_at_line(line)
/// returns position at the beginning of line @param{line}.
gint64 Document::pos_at_line(gint64 line)
{
GtkTextBuffer *buf = buffer(this);
if (line < 0 || line >= gtk_text_buffer_get_line_count(buf))
Error::raise("invalid line");
GtkTextIter iter;
gtk_text_buffer_get_iter_at_line(buf, &iter, line);
return gtk_text_iter_get_offset(&iter);
}
/// @item Document.pos_at_line_end(line)
/// returns position at the end of line @param{line}.
gint64 Document::pos_at_line_end(gint64 line)
{
GtkTextBuffer *buf = buffer(this);
if (line < 0 || line >= gtk_text_buffer_get_line_count(buf))
Error::raise("invalid line");
GtkTextIter iter;
gtk_text_buffer_get_iter_at_line(buf, &iter, line);
gtk_text_iter_forward_to_line_end(&iter);
return gtk_text_iter_get_offset(&iter);
}
/// @item Document.char_at_pos(pos)
/// returns character at position @param{pos} as string.
String Document::char_at_pos(gint64 pos)
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter iter;
get_iter(pos, buf, &iter);
if (gtk_text_iter_is_end(&iter))
Error::raise("can't get text at end of buffer");
gunichar c = gtk_text_iter_get_char(&iter);
char b[7];
b[g_unichar_to_utf8(c, b)] = 0;
return String(b);
}
/// @item Document.text()
/// returns whole document contents.
/// @item Document.text(start, end)
/// returns text in the range [@param{start}, @param{end}), @param{end} not
/// included. Example: @code{doc.text(doc.start_pos(), doc.end_pos())} is
/// equivalent @code{to doc.text()}.
String Document::text(const ArgList &args)
{
if (args.size() == 0)
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
gtk_text_buffer_get_bounds(buf, &start, &end);
return String::take_utf8(gtk_text_buffer_get_slice(buf, &start, &end, TRUE));
}
if (args.size() != 2)
Error::raise("either 0 or 2 arguments expected");
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
get_iter(args[0], buf, &start);
get_iter(args[1], buf, &end);
return String::take_utf8(gtk_text_buffer_get_slice(buf, &start, &end, TRUE));
}
/// @item Document.insert_text(text)
/// @item Document.insert_text(pos, text)
/// insert text into the document. If @param{pos} is not given, insert at
/// cursor position.
void Document::insert_text(const ArgList &args)
{
String text;
GtkTextIter iter;
GtkTextBuffer *buf = buffer(this);
if (args.size() == 1)
{
text = get_string(args[0]);
gtk_text_buffer_get_iter_at_mark(buf, &iter, gtk_text_buffer_get_insert(buf));
}
else if (args.size() == 2)
{
get_iter(args[0], buf, &iter);
text = get_string(args[1]);
}
else
{
Error::raise("one or two arguments expected");
}
gtk_text_buffer_insert(buf, &iter, text.utf8(), -1);
}
/// @item Document.replace_text(start, end, text)
/// replace text in the region [@param{start}, @param{end}). Equivalent to
/// @code{delete_text(start, end), insert_text(start, text)}.
void Document::replace_text(gint64 start_pos, gint64 end_pos, const String &text)
{
GtkTextIter start, end;
GtkTextBuffer *buf = buffer(this);
get_iter(start_pos, buf, &start);
get_iter(end_pos, buf, &end);
gtk_text_buffer_delete(buf, &start, &end);
gtk_text_buffer_insert(buf, &start, text.utf8(), -1);
}
/// @item Document.delete_text(start, end)
/// delete text in the region [@param{start}, @param{end}). Example:
/// @code{doc.delete_text(doc.start(), doc.end())} will delete all text in
/// @code{doc}.
void Document::delete_text(gint64 start_pos, gint64 end_pos)
{
GtkTextIter start, end;
GtkTextBuffer *buf = buffer(this);
get_iter(start_pos, buf, &start);
get_iter(end_pos, buf, &end);
gtk_text_buffer_delete(buf, &start, &end);
}
/// @item Document.append_text(text)
/// append text. Equivalent to @code{doc.insert_text(doc.end(), text)}.
void Document::append_text(const String &text)
{
GtkTextIter iter;
GtkTextBuffer *buf = buffer(this);
gtk_text_buffer_get_end_iter(buf, &iter);
gtk_text_buffer_insert(buf, &iter, text.utf8(), -1);
}
/// @item Document.clear()
/// delete all text in the document.
void Document::clear()
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
gtk_text_buffer_get_bounds(buf, &start, &end);
gtk_text_buffer_delete(buf, &start, &end);
}
/// @item Document.copy()
/// copy selected text to clipboard. If no text is selected then nothing
/// will happen, same as Ctrl-C key combination.
void Document::copy()
{
g_signal_emit_by_name(gobj(), "copy-clipboard");
}
/// @item Document.cut()
/// cut selected text to clipboard. If no text is selected then nothing
/// will happen, same as Ctrl-X key combination.
void Document::cut()
{
g_signal_emit_by_name(gobj(), "cut-clipboard");
}
/// @item Document.paste()
/// paste text from clipboard. It has the same effect as Ctrl-V key combination:
/// nothing happens if clipboard is empty, and selected text is replaced with
/// clipboard contents otherwise.
void Document::paste()
{
g_signal_emit_by_name(gobj(), "paste-clipboard");
}
/// @item Document.select_text(bounds_as_list)
/// @item Document.select_text(start, end)
/// select text, same as @method{set_selection()}.
void Document::select_text(const ArgList &args)
{
GtkTextIter start, end;
GtkTextBuffer *buf = buffer(this);
if (args.size() == 2)
{
get_iter(args[0], buf, &start);
get_iter(args[1], buf, &end);
}
else if (args.size() == 1)
{
get_iter_pair(args[0], buf, &start, &end);
}
else
{
Error::raise("exactly one or two arguments expected");
}
gtk_text_buffer_select_range(buf, &start, &end);
}
/// @item Document.select_lines(line)
/// select a line.
/// @item Document.select_lines(first, last)
/// select lines from @param{first} to @param{last}, @emph{including}
/// @param{last}.
void Document::select_lines(const ArgList &args)
{
Index first_line, last_line;
if (args.size() == 1)
{
first_line = last_line = get_index(args[0]);
}
else if (args.size() == 2)
{
first_line = get_index(args[0]);
last_line = get_index(args[1]);
}
else
{
Error::raise("exactly one or two arguments expected");
}
if (first_line.get() > last_line.get())
std::swap(first_line, last_line);
GtkTextBuffer *buf = buffer(this);
if (first_line.get() < 0 || first_line.get() >= gtk_text_buffer_get_line_count(buf))
Error::raise("invalid line");
if (last_line.get() < 0 || last_line.get() >= gtk_text_buffer_get_line_count(buf))
Error::raise("invalid line");
GtkTextIter start, end;
gtk_text_buffer_get_iter_at_line(buf, &start, first_line.get());
gtk_text_buffer_get_iter_at_line(buf, &end, last_line.get());
gtk_text_iter_forward_line(&end);
gtk_text_buffer_select_range(buf, &start, &end);
}
static void get_select_lines_range(const VariantArray &args, GtkTextBuffer *buf, GtkTextIter *start, GtkTextIter *end)
{
if (args.size() == 1)
{
get_iter(args[0], buf, start);
*end = *start;
}
else if (args.size() == 2)
{
get_iter(args[0], buf, start);
get_iter(args[1], buf, end);
}
else
{
Error::raise("exactly one or two arguments expected");
}
gtk_text_iter_order(start, end);
gtk_text_iter_forward_line(end);
}
/// @item Document.select_lines_at_pos(bounds_as_list)
/// @item Document.select_lines_at_pos(start, end)
/// select lines: similar to @method{select_text}, but select whole lines.
void Document::select_lines_at_pos(const ArgList &args)
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
get_select_lines_range(args, buf, &start, &end);
gtk_text_buffer_select_range(buf, &start, &end);
}
/// @item Document.select_all()
/// select all.
void Document::select_all()
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
gtk_text_buffer_get_bounds(buf, &start, &end);
gtk_text_buffer_select_range(buf, &start, &end);
}
/// @item Document.selected_text()
/// returns selected text.
String Document::selected_text()
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
gtk_text_buffer_get_selection_bounds(buf, &start, &end);
return String::take_utf8(gtk_text_buffer_get_slice(buf, &start, &end, TRUE));
}
static void get_selected_lines_bounds(GtkTextBuffer *buf, GtkTextIter *start, GtkTextIter *end, bool *cursor_at_next_line = 0)
{
if (cursor_at_next_line)
*cursor_at_next_line = false;
gtk_text_buffer_get_selection_bounds(buf, start, end);
gtk_text_iter_set_line_offset(start, 0);
if (gtk_text_iter_starts_line(end) && !gtk_text_iter_equal(start, end))
{
gtk_text_iter_backward_line(end);
if (cursor_at_next_line)
*cursor_at_next_line = true;
}
if (!gtk_text_iter_ends_line(end))
gtk_text_iter_forward_to_line_end(end);
}
/// @item Document.selected_lines()
/// returns selected lines as a list of strings, one string for each line,
/// line terminator characters not included. If nothing is selected, then
/// line at cursor is returned.
VariantArray Document::selected_lines()
{
GtkTextIter start, end;
GtkTextBuffer *buf = buffer(this);
get_selected_lines_bounds(buf, &start, &end);
char *text = gtk_text_buffer_get_slice(buf, &start, &end, TRUE);
char **lines = moo_splitlines(text);
VariantArray ar;
for (char **p = lines; p && *p; ++p)
ar.append(String::take_utf8(*p));
g_free(lines);
g_free(text);
return ar;
}
/// @item Document.delete_selected_text()
/// delete selected text, equivalent to @code{doc.delete_text(doc.cursor(),
/// doc.selection_bound())}.
void Document::delete_selected_text()
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
gtk_text_buffer_get_selection_bounds(buf, &start, &end);
gtk_text_buffer_delete(buf, &start, &end);
}
/// @item Document.delete_selected_lines()
/// delete selected lines. Similar to @method{delete_selected_text()} but
/// selection is extended to include whole lines. If nothing is selected,
/// then line at cursor is deleted.
void Document::delete_selected_lines()
{
GtkTextIter start, end;
GtkTextBuffer *buf = buffer(this);
get_selected_lines_bounds(buf, &start, &end);
gtk_text_iter_forward_line(&end);
gtk_text_buffer_delete(buf, &start, &end);
}
/// @item Document.replace_selected_text(text)
/// replace selected text with @param{text}. If nothing is selected,
/// @param{text} is inserted at cursor.
void Document::replace_selected_text(const String &text)
{
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
gtk_text_buffer_get_selection_bounds(buf, &start, &end);
gtk_text_buffer_delete(buf, &start, &end);
gtk_text_buffer_insert(buf, &start, text.utf8(), -1);
gtk_text_buffer_place_cursor(buf, &start);
}
static String join(const moo::Vector<String> &list, const String &sep)
{
std::stringstream ss;
for (int i = 0, c = list.size(); i < c; ++i)
{
if (i != 0)
ss << sep.utf8();
ss << list[i].utf8();
}
return ss.str();
}
/// @item Document.replace_selected_lines(text)
/// @item Document.replace_selected_lines(lines)
/// replace selected lines with @param{text}. Similar to
/// @method{replace_selected_text()}, but selection is extended to include
/// whole lines. If nothing is selected, then line at cursor is replaced.
void Document::replace_selected_lines(const Variant &repl)
{
String text;
switch (repl.vt())
{
case VtString:
text = repl.value<VtString>();
break;
case VtArray:
{
moo::Vector<String> lines = get_string_list(repl);
text = join(lines, "\n");
}
break;
case VtArgList:
{
moo::Vector<String> lines = get_string_list(repl);
text = join(lines, "\n");
}
break;
default:
Error::raisef("string or list of strings expected, got %s",
get_argument_type_name(repl.vt()));
break;
}
GtkTextBuffer *buf = buffer(this);
GtkTextIter start, end;
bool cursor_at_next_line;
get_selected_lines_bounds(buf, &start, &end, &cursor_at_next_line);
gtk_text_buffer_delete(buf, &start, &end);
gtk_text_buffer_insert(buf, &start, text.utf8(), -1);
if (cursor_at_next_line)
{
gtk_text_iter_forward_line(&start);
gtk_text_buffer_place_cursor(buf, &start);
}
}
/// @end table
} // namespace mom