Revision: 1584 http://svn.sourceforge.net/geany/?rev=1584&view=rev Author: ntrel Date: 2007-05-28 09:07:30 -0700 (Mon, 28 May 2007)
Log Message: ----------- Rename sci_cb.[hc] editor.[hc].
Modified Paths: -------------- trunk/ChangeLog trunk/po/POTFILES.in trunk/src/Makefile.am trunk/src/callbacks.c trunk/src/document.c trunk/src/highlighting.c trunk/src/keybindings.c trunk/src/main.c trunk/src/makefile.win32 trunk/src/search.c
Added Paths: ----------- trunk/src/editor.c trunk/src/editor.h
Removed Paths: ------------- trunk/src/sci_cb.c trunk/src/sci_cb.h
Modified: trunk/ChangeLog =================================================================== --- trunk/ChangeLog 2007-05-28 15:28:30 UTC (rev 1583) +++ trunk/ChangeLog 2007-05-28 16:07:30 UTC (rev 1584) @@ -4,6 +4,11 @@ Show current project name in window title. * configure.in, src/Makefile.am: Force debug mode for a SVN working copy. + * src/highlighting.c, src/keybindings.c, src/makefile.win32, + src/callbacks.c, src/sci_cb.c, src/sci_cb.h, src/search.c, + src/document.c, src/main.c, src/editor.c, src/Makefile.am, + src/editor.h, po/POTFILES.in: + Rename sci_cb.[hc] editor.[hc].
2007-05-26 Nick Treleaven nick.treleaven@btinternet.com
Modified: trunk/po/POTFILES.in =================================================================== --- trunk/po/POTFILES.in 2007-05-28 15:28:30 UTC (rev 1583) +++ trunk/po/POTFILES.in 2007-05-28 16:07:30 UTC (rev 1584) @@ -18,7 +18,7 @@ src/notebook.c src/prefs.c src/project.c -src/sci_cb.c +src/editor.c src/sciwrappers.c src/search.c src/socket.c
Modified: trunk/src/Makefile.am =================================================================== --- trunk/src/Makefile.am 2007-05-28 15:28:30 UTC (rev 1583) +++ trunk/src/Makefile.am 2007-05-28 16:07:30 UTC (rev 1584) @@ -23,7 +23,7 @@ notebook.c notebook.h \ prefs.c prefs.h \ project.c project.h \ - sci_cb.c sci_cb.h \ + editor.c editor.h \ sciwrappers.c sciwrappers.h \ search.c search.h \ socket.c socket.h \
Modified: trunk/src/callbacks.c =================================================================== --- trunk/src/callbacks.c 2007-05-28 15:28:30 UTC (rev 1583) +++ trunk/src/callbacks.c 2007-05-28 16:07:30 UTC (rev 1584) @@ -41,7 +41,7 @@ #include "keyfile.h" #include "document.h" #include "sciwrappers.h" -#include "sci_cb.h" +#include "editor.h" #include "ui_utils.h" #include "utils.h" #include "dialogs.h" @@ -1249,7 +1249,7 @@ if (idx == -1 || ! doc_list[idx].is_valid) return;
- sci_cb_find_current_word(doc_list[idx].sci, pos, colour, sizeof colour, NULL); + editor_find_current_word(doc_list[idx].sci, pos, colour, sizeof colour, NULL); dialogs_show_color(colour); }
@@ -1477,7 +1477,7 @@
verify_click_pos(idx); // make sure that the click_pos is valid
- sci_cb_insert_multiline_comment(idx); + editor_insert_multiline_comment(idx); }
@@ -1829,7 +1829,7 @@ { gint idx = document_get_cur_idx(); if (idx == -1 || ! doc_list[idx].is_valid) return; - sci_cb_do_comment(idx, -1, FALSE); + editor_do_comment(idx, -1, FALSE); }
@@ -1839,7 +1839,7 @@ { gint idx = document_get_cur_idx(); if (idx == -1 || ! doc_list[idx].is_valid) return; - sci_cb_do_uncomment(idx, -1); + editor_do_uncomment(idx, -1); }
@@ -1850,7 +1850,7 @@ { gint idx = document_get_cur_idx(); if (idx == -1 || ! doc_list[idx].is_valid) return; - sci_cb_do_comment_toggle(idx); + editor_do_comment_toggle(idx); }
@@ -2067,7 +2067,7 @@ { // use the word at current cursor position gchar word[GEANY_MAX_WORD_LENGTH];
- sci_cb_find_current_word(doc_list[idx].sci, -1, word, sizeof(word), GEANY_WORDCHARS"./"); + editor_find_current_word(doc_list[idx].sci, -1, word, sizeof(word), GEANY_WORDCHARS"./"); if (word[0] != '\0') filename = g_strdup(word); }
Modified: trunk/src/document.c =================================================================== --- trunk/src/document.c 2007-05-28 15:28:30 UTC (rev 1583) +++ trunk/src/document.c 2007-05-28 16:07:30 UTC (rev 1584) @@ -53,7 +53,7 @@ #include "document.h" #include "support.h" #include "sciwrappers.h" -#include "sci_cb.h" +#include "editor.h" #include "dialogs.h" #include "msgwindow.h" #include "templates.h" @@ -1571,14 +1571,14 @@ guint n; const GString *s;
- if (sci != NULL && sci_cb_lexer_get_type_keyword_idx(sci_get_lexer(sci)) == -1) + if (sci != NULL && editor_lexer_get_type_keyword_idx(sci_get_lexer(sci)) == -1) return FALSE;
if (! get_project_typenames(&s)) { // typenames have not changed if (s != NULL && sci != NULL) { - gint keyword_idx = sci_cb_lexer_get_type_keyword_idx(sci_get_lexer(sci)); + gint keyword_idx = editor_lexer_get_type_keyword_idx(sci_get_lexer(sci));
sci_set_keywords(sci, keyword_idx, s->str); if (! delay_colourise) @@ -1596,7 +1596,7 @@
if (wid) { - gint keyword_idx = sci_cb_lexer_get_type_keyword_idx(sci_get_lexer(wid)); + gint keyword_idx = editor_lexer_get_type_keyword_idx(sci_get_lexer(wid));
if (keyword_idx > 0) { @@ -2214,7 +2214,7 @@
n = g_array_index(doc_indexes, gint, i); sci = doc_list[n].sci; - if (! recolour || (sci && sci_cb_lexer_get_type_keyword_idx(sci_get_lexer(sci)) == -1)) + if (! recolour || (sci && editor_lexer_get_type_keyword_idx(sci_get_lexer(sci)) == -1)) { doc_set[n] = FALSE; }
Copied: trunk/src/editor.c (from rev 1583, trunk/src/sci_cb.c) =================================================================== --- trunk/src/editor.c (rev 0) +++ trunk/src/editor.c 2007-05-28 16:07:30 UTC (rev 1584) @@ -0,0 +1,2296 @@ +/* + * editor.c - this file is part of Geany, a fast and lightweight IDE + * + * Copyright 2005-2007 Enrico Tröger enrico.troeger@uvena.de + * Copyright 2006-2007 Nick Treleaven nick.treleaven@btinternet.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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id$ + */ + +/* + * Callbacks for the Scintilla widget (ScintillaObject). + * Most important is the sci-notify callback, handled in on_editor_notification(). + * This includes auto-indentation, comments, auto-completion, calltips, etc. + * Also some general Scintilla-related functions. + */ + +#include <ctype.h> +#include <string.h> + +#include "SciLexer.h" +#include "geany.h" + +#include "editor.h" +#include "document.h" +#include "sciwrappers.h" +#include "ui_utils.h" +#include "utils.h" +#include "symbols.h" + + +static gchar current_word[GEANY_MAX_WORD_LENGTH]; // holds word under the mouse or keyboard cursor + +EditorInfo editor_info = {current_word, -1}; + +static struct +{ + gchar *text; + gboolean set; + gchar *last_word; + guint tag_index; +} calltip = {NULL, FALSE, NULL, 0}; + +static gchar indent[100]; + + +static void on_new_line_added(ScintillaObject *sci, gint idx); +static gboolean handle_xml(ScintillaObject *sci, gchar ch, gint idx); +static void get_indent(ScintillaObject *sci, gint pos, gboolean use_this_line); +static void auto_multiline(ScintillaObject *sci, gint pos); +static gboolean is_comment(gint lexer, gint style); +static void scroll_to_line(ScintillaObject *sci, gint line, gfloat percent_of_view); +static void auto_close_bracket(ScintillaObject *sci, gint pos, gchar c); + + +// calls the edit popup menu in the editor +gboolean +on_editor_button_press_event (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + gint idx = GPOINTER_TO_INT(user_data); + editor_info.click_pos = sci_get_position_from_xy(doc_list[idx].sci, event->x, event->y, FALSE); + + if (event->button == 1) + { + if (GDK_BUTTON_PRESS==event->type && app->pref_editor_disable_dnd) + { + gint ss = sci_get_selection_start(doc_list[idx].sci); + sci_set_selection_end(doc_list[idx].sci, ss); + } + return utils_check_disk_status(idx, FALSE); + } + + if (event->button == 3) + { + editor_find_current_word(doc_list[idx].sci, editor_info.click_pos, + current_word, sizeof current_word, NULL); + + ui_update_popup_goto_items((current_word[0] != '\0') ? TRUE : FALSE); + ui_update_popup_copy_items(idx); + ui_update_insert_include_item(idx, 0); + gtk_menu_popup(GTK_MENU(app->popup_menu), NULL, NULL, NULL, NULL, event->button, event->time); + + return TRUE; + } + return FALSE; +} + + +typedef struct SCNotification SCNotification; + +static void on_margin_click(ScintillaObject *sci, SCNotification *nt) +{ + // left click to marker margin marks the line + if (nt->margin == 1) + { + gint line = sci_get_line_from_position(sci, nt->position); + gboolean set = sci_is_marker_set_at_line(sci, line, 1); + + //sci_marker_delete_all(doc_list[idx].sci, 1); + sci_set_marker_at_line(sci, line, ! set, 1); // toggle the marker + } + // left click on the folding margin to toggle folding state of current line + else if (nt->margin == 2 && app->pref_editor_folding) + { + gint line = SSM(sci, SCI_LINEFROMPOSITION, nt->position, 0); + + SSM(sci, SCI_TOGGLEFOLD, line, 0); + if (app->pref_editor_unfold_all_children && + SSM(sci, SCI_GETLINEVISIBLE, line + 1, 0)) + { // unfold all children of the current fold point + gint last_line = SSM(sci, SCI_GETLASTCHILD, line, -1); + gint i; + + for (i = line; i < last_line; i++) + { + if (! SSM(sci, SCI_GETLINEVISIBLE, i, 0)) + { + SSM(sci, SCI_TOGGLEFOLD, SSM(sci, SCI_GETFOLDPARENT, i, 0), 0); + } + } + } + } +} + + +static void on_update_ui(gint idx, G_GNUC_UNUSED SCNotification *nt) +{ + ScintillaObject *sci = doc_list[idx].sci; + gint pos = sci_get_current_position(sci); + + // undo / redo menu update + ui_update_popup_reundo_items(idx); + + // brace highlighting + editor_highlight_braces(sci, pos); + + ui_update_statusbar(idx, pos); + + /* Visible lines are only laid out accurately once [SCN_UPDATEUI] is sent, + * so we need to only call sci_scroll_to_line here, because the document + * may have line wrapping and folding enabled. + * http://scintilla.sourceforge.net/ScintillaDoc.html#LineWrapping */ + if (doc_list[idx].scroll_percent > 0.0F) + { + scroll_to_line(sci, -1, doc_list[idx].scroll_percent); + doc_list[idx].scroll_percent = -1.0F; // disable further scrolling + } +#if 0 + /// experimental code for inverting selections + { + gint i; + for (i = SSM(sci, SCI_GETSELECTIONSTART, 0, 0); i < SSM(sci, SCI_GETSELECTIONEND, 0, 0); i++) + { + // need to get colour from getstyleat(), but how? + SSM(sci, SCI_STYLESETFORE, STYLE_DEFAULT, 0); + SSM(sci, SCI_STYLESETBACK, STYLE_DEFAULT, 0); + } + + sci_get_style_at(sci, pos); + } +#endif +} + + +static void on_char_added(gint idx, SCNotification *nt) +{ + ScintillaObject *sci = doc_list[idx].sci; + gint pos = sci_get_current_position(sci); + + switch (nt->ch) + { + case '\r': + { // simple indentation (only for CR format) + if (sci_get_eol_mode(sci) == SC_EOL_CR) + on_new_line_added(sci, idx); + break; + } + case '\n': + { // simple indentation (for CR/LF and LF format) + on_new_line_added(sci, idx); + break; + } + case '>': + case '/': + { // close xml-tags + handle_xml(sci, nt->ch, idx); + break; + } + case '(': + { // show calltips + editor_show_calltip(idx, --pos); + break; + } + case ')': + { // hide calltips + if (SSM(sci, SCI_CALLTIPACTIVE, 0, 0)) + { + SSM(sci, SCI_CALLTIPCANCEL, 0, 0); + } + g_free(calltip.text); + calltip.text = NULL; + calltip.set = FALSE; + break; + } + case '[': + case '{': + { // Tex auto-closing + if (sci_get_lexer(sci) == SCLEX_LATEX) + { + auto_close_bracket(sci, pos, nt->ch); // Tex auto-closing + editor_show_calltip(idx, --pos); + } + break; + } + case '}': + { // closing bracket handling + if (doc_list[idx].use_auto_indention && + app->pref_editor_indention_mode == INDENT_ADVANCED) + editor_close_block(idx, pos - 1); + break; + } + default: editor_start_auto_complete(idx, pos, FALSE); + } +} + + +// callback func called by all editors when a signal arises +void on_editor_notification(GtkWidget *editor, gint scn, gpointer lscn, gpointer user_data) +{ + SCNotification *nt; + ScintillaObject *sci; + gint idx; + + idx = GPOINTER_TO_INT(user_data); + sci = doc_list[idx].sci; + + nt = lscn; + switch (nt->nmhdr.code) + { + case SCN_SAVEPOINTLEFT: + { + doc_list[idx].changed = TRUE; + document_set_text_changed(idx); + break; + } + case SCN_SAVEPOINTREACHED: + { + doc_list[idx].changed = FALSE; + document_set_text_changed(idx); + break; + } + case SCN_MODIFYATTEMPTRO: + { + utils_beep(); + break; + } + case SCN_MARGINCLICK: + on_margin_click(sci, nt); + break; + + case SCN_UPDATEUI: + on_update_ui(idx, nt); + break; + + case SCN_MODIFIED: + { + if (nt->modificationType & SC_STARTACTION && ! app->ignore_callback) + { + // get notified about undo changes + document_undo_add(idx, UNDO_SCINTILLA, NULL); + } + break; + } + case SCN_CHARADDED: + on_char_added(idx, nt); + break; + + case SCN_USERLISTSELECTION: + { + if (nt->listType == 1) + { + gint pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) nt->text); + } + else if (nt->listType == 2) + { + gint start, pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + start = pos; + while (start > 0 && sci_get_char_at(sci, --start) != '&') ; + + SSM(sci, SCI_INSERTTEXT, pos - 1, (sptr_t) nt->text); + } + break; + } + case SCN_AUTOCSELECTION: + { + // now that autocomplete is finishing, reshow calltips if they were showing + if (calltip.set) + { + gint pos = sci_get_current_position(sci); + SSM(sci, SCI_CALLTIPSHOW, pos, (sptr_t) calltip.text); + // now autocompletion has been cancelled, so do it manually + sci_set_selection_start(sci, nt->lParam); + sci_set_selection_end(sci, pos); + sci_replace_sel(sci, ""); // clear root of word + SSM(sci, SCI_INSERTTEXT, nt->lParam, (sptr_t) nt->text); + sci_goto_pos(sci, nt->lParam + strlen(nt->text), FALSE); + } + break; + } +#ifdef GEANY_DEBUG + case SCN_STYLENEEDED: + { + geany_debug("style"); + break; + } +#endif + case SCN_URIDROPPED: + { + if (nt->text != NULL) + { + document_open_file_list(nt->text, -1); + } + break; + } + case SCN_CALLTIPCLICK: + { + if (nt->position > 0) + { + switch (nt->position) + { + case 1: // up arrow + if (calltip.tag_index > 0) + calltip.tag_index--; + break; + + case 2: calltip.tag_index++; break; // down arrow + } + editor_show_calltip(idx, -1); + } + break; + } + } +} + + +static void on_new_line_added(ScintillaObject *sci, gint idx) +{ + gint pos = sci_get_current_position(sci); + + // simple indentation + if (doc_list[idx].use_auto_indention) + { + get_indent(sci, pos, FALSE); + sci_add_text(sci, indent); + + if (app->pref_editor_indention_mode == INDENT_ADVANCED) + { + // add extra indentation for Python after colon + if (FILETYPE_ID(doc_list[idx].file_type) == GEANY_FILETYPES_PYTHON && + sci_get_char_at(sci, pos - 2) == ':' && + sci_get_style_at(sci, pos - 2) == SCE_P_OPERATOR) + { + // creates and inserts one tabulator sign or whitespace of the amount of the tab width + gchar *text = utils_get_whitespace(app->pref_editor_tab_width, FALSE); + sci_add_text(sci, text); + g_free(text); + } + } + } + + if (app->pref_editor_auto_complete_constructs) + { + // " * " auto completion in multiline C/C++/D/Java comments + auto_multiline(sci, pos); + + editor_auto_latex(idx, pos); + } +} + + +static gboolean lexer_has_braces(ScintillaObject *sci) +{ + gint lexer = SSM(sci, SCI_GETLEXER, 0, 0); + + switch (lexer) + { + case SCLEX_CPP: + case SCLEX_D: + case SCLEX_HTML: // for PHP & JS + case SCLEX_PASCAL: // for multiline comments? + case SCLEX_BASH: + case SCLEX_PERL: + case SCLEX_TCL: + return TRUE; + default: + return FALSE; + } +} + + +// in place indentation of one tab or equivalent spaces +static void do_indent(gchar *buf, gsize len, guint *idx) +{ + guint j = *idx; + + if (app->pref_editor_use_tabs) + { + if (j < len - 1) // leave room for a \0 terminator. + buf[j++] = '\t'; + } + else + { // insert as many spaces as a tab would take + guint k; + for (k = 0; k < (guint) app->pref_editor_tab_width && k < len - 1; k++) + buf[j++] = ' '; + } + *idx = j; +} + + +/* "use_this_line" to auto-indent only if it is a real new line + * and ignore the case of editor_close_block */ +static void get_indent(ScintillaObject *sci, gint pos, gboolean use_this_line) +{ + guint i, len, j = 0; + gint prev_line; + gchar *linebuf; + + prev_line = sci_get_line_from_position(sci, pos); + + if (! use_this_line) prev_line--; + len = sci_get_line_length(sci, prev_line); + linebuf = sci_get_line(sci, prev_line); + + for (i = 0; i < len && j <= (sizeof(indent) - 1); i++) + { + if (linebuf[i] == ' ' || linebuf[i] == '\t') // simple indentation + indent[j++] = linebuf[i]; + else if (app->pref_editor_indention_mode != INDENT_ADVANCED) + break; + else if (use_this_line) + break; + else // editor_close_block + { + if (! lexer_has_braces(sci)) + break; + + if (linebuf[i] == '{') + { + do_indent(indent, sizeof(indent), &j); + break; + } + else + { + gint k = len - 1; + + while (k > 0 && isspace(linebuf[k])) k--; + + // if last non-whitespace character is a { increase indentation by a tab + // e.g. for (...) { + if (linebuf[k] == '{') + { + do_indent(indent, sizeof(indent), &j); + } + break; + } + } + } + indent[j] = '\0'; + g_free(linebuf); +} + + +static void auto_close_bracket(ScintillaObject *sci, gint pos, gchar c) +{ + if (! app->pref_editor_auto_complete_constructs || SSM(sci, SCI_GETLEXER, 0, 0) != SCLEX_LATEX) + return; + + if (c == '[') + { + sci_add_text(sci, "]"); + } + else if (c == '{') + { + sci_add_text(sci, "}"); + } + sci_set_current_position(sci, pos, TRUE); +} + + +/* Finds a corresponding matching brace to the given pos + * (this is taken from Scintilla Editor.cxx, + * fit to work with editor_close_block) */ +static gint brace_match(ScintillaObject *sci, gint pos) +{ + gchar chBrace = sci_get_char_at(sci, pos); + gchar chSeek = utils_brace_opposite(chBrace); + gchar chAtPos; + gint direction = -1; + gint styBrace; + gint depth = 1; + gint styAtPos; + + styBrace = sci_get_style_at(sci, pos); + + if (utils_is_opening_brace(chBrace)) + direction = 1; + + pos = pos + direction; + while ((pos >= 0) && (pos < sci_get_length(sci))) + { + chAtPos = sci_get_char_at(sci, pos - 1); + styAtPos = sci_get_style_at(sci, pos); + + if ((pos > sci_get_end_styled(sci)) || (styAtPos == styBrace)) + { + if (chAtPos == chBrace) + depth++; + if (chAtPos == chSeek) + depth--; + if (depth == 0) + return pos; + } + pos = pos + direction; + } + return -1; +} + + +/* Called after typing '}', if pref_editor_indention_mode is INDENT_ADVANCED. */ +void editor_close_block(gint idx, gint pos) +{ + gint x = 0, cnt = 0; + gint start_brace, line, line_len, eol_char_len; + gchar *text, *line_buf; + ScintillaObject *sci; + + if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return; + + sci = doc_list[idx].sci; + + if (! lexer_has_braces(sci)) + return; + + line = sci_get_line_from_position(sci, pos); + line_len = sci_get_line_length(sci, line); + // set eol_char_len to 0 if on last line, because there is no EOL char + eol_char_len = (line == (SSM(sci, SCI_GETLINECOUNT, 0, 0) - 1)) ? 0 : + utils_get_eol_char_len(document_find_by_sci(sci)); + + // check that the line is empty, to not kill text in the line + line_buf = sci_get_line(sci, line); + line_buf[line_len - eol_char_len] = '\0'; + while (x < (line_len - eol_char_len)) + { + if (isspace(line_buf[x])) cnt++; + x++; + } + g_free(line_buf); + + if ((line_len - eol_char_len - 1) != cnt) return; + + start_brace = brace_match(sci, pos); // same as sci_find_bracematch (Document::BraceMatch)? + + if (start_brace >= 0) + { + gint line_start; + + get_indent(sci, start_brace, TRUE); + text = g_strconcat(indent, "}", NULL); + line_start = sci_get_position_from_line(sci, line); + sci_set_anchor(sci, line_start); + SSM(sci, SCI_REPLACESEL, 0, (sptr_t) text); + g_free(text); + } + else + if (sci_get_lexer(sci) == SCLEX_HTML || sci_get_lexer(sci) == SCLEX_TCL) + { /* For TCL & PHP brace_match doesn't work here (maybe lexer bugs?), + * so this is a simple workaround. */ + gint line_indent = sci_get_line_indentation(sci, line); + gint last_indent = sci_get_line_indentation(sci, line - 1); + + if (line_indent < last_indent) + return; + line_indent -= app->pref_editor_tab_width; + line_indent = MAX(0, line_indent); + sci_set_line_indentation(sci, line, line_indent); + } +} + + +/* Reads the word at given cursor position and writes it into the given buffer. The buffer will be + * NULL terminated in any case, even when the word is truncated because wordlen is too small. + * position can be -1, then the current position is used. + * wc are the wordchars to use, if NULL, GEANY_WORDCHARS will be used */ +void editor_find_current_word(ScintillaObject *sci, gint pos, gchar *word, size_t wordlen, + const gchar *wc) +{ + gint line, line_start, startword, endword; + gchar *chunk; + + if (pos == -1) + pos = sci_get_current_position(sci); + + line = sci_get_line_from_position(sci, pos); + line_start = sci_get_position_from_line(sci, line); + startword = pos - line_start; + endword = pos - line_start; + + word[0] = '\0'; + chunk = sci_get_line(sci, line); + + if (wc == NULL) + wc = GEANY_WORDCHARS; + + while (startword > 0 && strchr(wc, chunk[startword - 1])) + startword--; + while (chunk[endword] && strchr(wc, chunk[endword])) + endword++; + if(startword == endword) + return; + + chunk[endword] = '\0'; + + g_strlcpy(word, chunk + startword, wordlen); //ensure null terminated + g_free(chunk); +} + + +static gint find_previous_brace(ScintillaObject *sci, gint pos) +{ + gchar c; + gint orig_pos = pos; + + c = SSM(sci, SCI_GETCHARAT, pos, 0); + while (pos >= 0 && pos > orig_pos - 300) + { + c = SSM(sci, SCI_GETCHARAT, pos, 0); + pos--; + if (utils_is_opening_brace(c)) return pos; + } + return -1; +} + + +static gint find_start_bracket(ScintillaObject *sci, gint pos) +{ + gchar c; + gint brackets = 0; + gint orig_pos = pos; + + c = SSM(sci, SCI_GETCHARAT, pos, 0); + while (pos > 0 && pos > orig_pos - 300) + { + c = SSM(sci, SCI_GETCHARAT, pos, 0); + if (c == ')') brackets++; + else if (c == '(') brackets--; + pos--; + if (brackets < 0) return pos; // found start bracket + } + return -1; +} + + +static gboolean append_calltip(GString *str, const TMTag *tag, filetype_id ft_id) +{ + if (! tag->atts.entry.arglist) return FALSE; + + if (tag->atts.entry.var_type) + { + guint i; + + g_string_append(str, tag->atts.entry.var_type); + for (i = 0; i < tag->atts.entry.pointerOrder; i++) + { + g_string_append_c(str, '*'); + } + g_string_append_c(str, ' '); + } + if (tag->atts.entry.scope) + { + const gchar *cosep = symbols_get_context_separator(ft_id); + + g_string_append(str, tag->atts.entry.scope); + g_string_append(str, cosep); + } + g_string_append(str, tag->name); + g_string_append_c(str, ' '); + g_string_append(str, tag->atts.entry.arglist); + + return TRUE; +} + + +static gchar *find_calltip(const gchar *word, filetype *ft) +{ + const GPtrArray *tags; + const gint arg_types = tm_tag_function_t | tm_tag_prototype_t | + tm_tag_method_t | tm_tag_macro_with_arg_t; + TMTagAttrType *attrs = NULL; + TMTag *tag; + GString *str = NULL; + guint i; + + g_return_val_if_fail(ft && word && *word, NULL); + + tags = tm_workspace_find(word, arg_types | tm_tag_class_t, attrs, FALSE, ft->lang); + if (tags->len == 0) + return NULL; + + tag = TM_TAG(tags->pdata[0]); + + if (tag->type == tm_tag_class_t && FILETYPE_ID(ft) == GEANY_FILETYPES_D) + { + // user typed e.g. 'new Classname(' so lookup D constructor Classname::this() + tags = tm_workspace_find_scoped("this", tag->name, + arg_types, attrs, FALSE, ft->lang, TRUE); + if (tags->len == 0) + return NULL; + } + + // remove tags with no argument list + for (i = 0; i < tags->len; i++) + { + tag = TM_TAG(tags->pdata[i]); + + if (! tag->atts.entry.arglist) + tags->pdata[i] = NULL; + } + tm_tags_prune((GPtrArray *) tags); + if (tags->len == 0) + return NULL; + else + { // remove duplicate calltips + TMTagAttrType sort_attr[] = {tm_tag_attr_name_t, tm_tag_attr_scope_t, + tm_tag_attr_arglist_t, 0}; + + tm_tags_sort((GPtrArray *) tags, sort_attr, TRUE); + } + + // if the current word has changed since last time, start with the first tag match + if (! utils_str_equal(word, calltip.last_word)) + calltip.tag_index = 0; + // cache the current word for next time + g_free(calltip.last_word); + calltip.last_word = g_strdup(word); + calltip.tag_index = MIN(calltip.tag_index, tags->len - 1); // ensure tag_index is in range + + for (i = calltip.tag_index; i < tags->len; i++) + { + tag = TM_TAG(tags->pdata[i]); + + if (str == NULL) + { + str = g_string_new(NULL); + if (calltip.tag_index > 0) + g_string_prepend(str, "\001 "); // up arrow + append_calltip(str, tag, FILETYPE_ID(ft)); + } + else // add a down arrow + { + if (calltip.tag_index > 0) // already have an up arrow + g_string_insert_c(str, 1, '\002'); + else + g_string_prepend(str, "\002 "); + break; + } + } + if (str) + { + gchar *result = str->str; + + g_string_free(str, FALSE); + return result; + } + return NULL; +} + + +// use pos = -1 to search for the previous unmatched open bracket. +gboolean editor_show_calltip(gint idx, gint pos) +{ + gint orig_pos = pos; // the position for the calltip + gint lexer; + gint style; + gchar word[GEANY_MAX_WORD_LENGTH]; + gchar *str; + ScintillaObject *sci; + + if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return FALSE; + sci = doc_list[idx].sci; + + lexer = SSM(sci, SCI_GETLEXER, 0, 0); + + if (pos == -1) + { + // position of '(' is unknown, so go backwards from current position to find it + pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + pos--; + orig_pos = pos; + pos = (lexer == SCLEX_LATEX) ? find_previous_brace(sci, pos) : + find_start_bracket(sci, pos); + if (pos == -1) return FALSE; + } + + style = SSM(sci, SCI_GETSTYLEAT, pos, 0); + if (is_comment(lexer, style)) + return FALSE; + // never show a calltip in a PHP file outside of the <? ?> tags + if (lexer == SCLEX_HTML && ! (style >= SCE_HPHP_DEFAULT && style <= SCE_HPHP_OPERATOR)) + return FALSE; + + word[0] = '\0'; + editor_find_current_word(sci, pos - 1, word, sizeof word, NULL); + if (word[0] == '\0') return FALSE; + + str = find_calltip(word, doc_list[idx].file_type); + if (str) + { + g_free(calltip.text); // free the old calltip + calltip.text = str; + calltip.set = TRUE; + utils_wrap_string(calltip.text, -1); + SSM(sci, SCI_CALLTIPSHOW, orig_pos, (sptr_t) calltip.text); + return TRUE; + } + return FALSE; +} + + +static void show_autocomplete(ScintillaObject *sci, gint rootlen, const gchar *words) +{ + // store whether a calltip is showing, so we can reshow it after autocompletion + calltip.set = SSM(sci, SCI_CALLTIPACTIVE, 0, 0); + SSM(sci, SCI_AUTOCSHOW, rootlen, (sptr_t) words); +} + + +static gboolean +autocomplete_html(ScintillaObject *sci, const gchar *root, gsize rootlen) +{ // HTML entities auto completion + guint i, j = 0; + GString *words; + const gchar **entities = symbols_get_html_entities(); + + if (*root != '&' || entities == NULL) return FALSE; + + words = g_string_sized_new(500); + for (i = 0; ; i++) + { + if (entities[i] == NULL) break; + else if (entities[i][0] == '#') continue; + + if (! strncmp(entities[i], root, rootlen)) + { + if (j++ > 0) g_string_append_c(words, ' '); + g_string_append(words, entities[i]); + } + } + if (words->len > 0) show_autocomplete(sci, rootlen, words->str); + g_string_free(words, TRUE); + return TRUE; +} + + +static gboolean +autocomplete_tags(gint idx, gchar *root, gsize rootlen) +{ // PHP, LaTeX, C, C++, D and Java tag autocompletion + TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 }; + const GPtrArray *tags; + ScintillaObject *sci; + + if (! DOC_IDX_VALID(idx) || doc_list[idx].file_type == NULL) + return FALSE; + sci = doc_list[idx].sci; + + tags = tm_workspace_find(root, tm_tag_max_t, attrs, TRUE, doc_list[idx].file_type->lang); + if (NULL != tags && tags->len > 0) + { + GString *words = g_string_sized_new(150); + guint j; + + for (j = 0; ((j < tags->len) && (j < GEANY_MAX_AUTOCOMPLETE_WORDS)); ++j) + { + if (j > 0) g_string_append_c(words, ' '); + g_string_append(words, ((TMTag *) tags->pdata[j])->name); + } + show_autocomplete(sci, rootlen, words->str); + g_string_free(words, TRUE); + } + return TRUE; +} + + +gboolean editor_start_auto_complete(gint idx, gint pos, gboolean force) +{ + gint line, line_start, line_len, line_pos, current, rootlen, startword, lexer, style; + gchar *linebuf, *root; + ScintillaObject *sci; + gboolean ret = FALSE; + gchar *wordchars; + filetype *ft; + + if ((! app->pref_editor_auto_complete_symbols && ! force) || + ! DOC_IDX_VALID(idx) || doc_list[idx].file_type == NULL) + return FALSE; + + sci = doc_list[idx].sci; + ft = doc_list[idx].file_type; + + line = sci_get_line_from_position(sci, pos); + line_start = sci_get_position_from_line(sci, line); + line_len = sci_get_line_length(sci, line); + line_pos = pos - line_start - 1; + current = pos - line_start; + startword = current; + lexer = SSM(sci, SCI_GETLEXER, 0, 0); + style = SSM(sci, SCI_GETSTYLEAT, pos, 0); + + // don't autocomplete in comments and strings + if (is_comment(lexer, style)) + return FALSE; + + linebuf = sci_get_line(sci, line); + + if (ft->id == GEANY_FILETYPES_LATEX) + wordchars = GEANY_WORDCHARS"\"; // add \ to word chars if we are in a LaTeX file + else if (ft->id == GEANY_FILETYPES_HTML || ft->id == GEANY_FILETYPES_PHP) + wordchars = GEANY_WORDCHARS"&"; // add & to word chars if we are in a PHP or HTML file + else + wordchars = GEANY_WORDCHARS; + + // find the start of the current word + while ((startword > 0) && (strchr(wordchars, linebuf[startword - 1]))) + startword--; + linebuf[current] = '\0'; + root = linebuf + startword; + rootlen = current - startword; + + // entity autocompletion always in a HTML file, in a PHP file only when we are outside of <? ?> + if (ft->id == GEANY_FILETYPES_HTML || + (ft->id == GEANY_FILETYPES_PHP && (style < SCE_HPHP_DEFAULT || style > SCE_HPHP_OPERATOR))) + ret = autocomplete_html(sci, root, rootlen); + else + { + // force is set when called by keyboard shortcut, otherwise start at the 4th char + if (force || rootlen >= 4) + ret = autocomplete_tags(idx, root, rootlen); + } + + g_free(linebuf); + return ret; +} + + +void editor_auto_latex(gint idx, gint pos) +{ + ScintillaObject *sci; + + if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return; + sci = doc_list[idx].sci; + + if (sci_get_char_at(sci, pos - 2) == '}') + { + gchar *eol, *buf, *construct; + gchar env[50]; + gint line = sci_get_line_from_position(sci, pos - 2); + gint line_len = sci_get_line_length(sci, line); + gint i, start; + + // get the line + buf = sci_get_line(sci, line); + + // get to the first non-blank char (some kind of ltrim()) + start = 0; + //while (isspace(buf[i++])) start++; + while (isspace(buf[start])) start++; + + // check for begin + if (strncmp(buf + start, "\begin", 6) == 0) + { + gchar full_cmd[15]; + guint j = 0; + + // take also "\begingroup" (or whatever there can be) and append "\endgroup" and so on. + i = start + 6; + while (i < line_len && buf[i] != '{' && j < (sizeof(full_cmd) - 1)) + { // copy all between "\begin" and "{" to full_cmd + full_cmd[j] = buf[i]; + i++; + j++; + } + full_cmd[j] = '\0'; + + // go through the line and get the environment + for (i = start + j; i < line_len; i++) + { + if (buf[i] == '{') + { + j = 0; + i++; + while (buf[i] != '}' && j < (sizeof(env) - 1)) + { // this could be done in a shorter way, but so it remains readable ;-) + env[j] = buf[i]; + j++; + i++; + } + env[j] = '\0'; + break; + } + } + + // get the indention + if (doc_list[idx].use_auto_indention) get_indent(sci, pos, TRUE); + eol = g_strconcat(utils_get_eol_char(idx), indent, NULL); + + construct = g_strdup_printf("%s\end%s{%s}", eol, full_cmd, env); + + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) construct); + sci_goto_pos(sci, pos + 1, TRUE); + g_free(construct); + g_free(eol); + } + // later there could be some else ifs for other keywords + + g_free(buf); + } +} + + +/* This should use a string with the current word instead of buf, but we will replace this + * code with user-defined construct completion. */ +static gboolean complete_constructs(gint idx, gint pos, const gchar *buf, const gchar *space, + const gchar *eol) +{ + gboolean result; + gchar *construct = NULL; + gint space_len = strlen(space); + ScintillaObject *sci = doc_list[idx].sci; + + // "pattern", buf + x, y -> x + y = 15, because buf is (pos - 15)...(pos) = 15 + if (! strncmp("if", buf + 13, 2)) + { + if (! isspace(*(buf + 12))) + return FALSE; + + construct = g_strdup_printf("()%s{%s%s%s}%s", eol, eol, space, eol, eol); + + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) construct); + sci_goto_pos(sci, pos + 1, TRUE); + } + else if (! strncmp("else", buf + 11, 4)) + { + if (! isspace(*(buf + 10))) + return FALSE; + + construct = g_strdup_printf("%s{%s%s%s}%s", eol, eol, space, eol, eol); + + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) construct); + sci_goto_pos(sci, pos + 3 + space_len + (2 * strlen(indent)), TRUE); + } + else if (! strncmp("for", buf + 12, 3)) + { + gchar *var; + gint contruct_len; + + if (! isspace(*(buf + 11))) + return FALSE; + + if (doc_list[idx].file_type->id == GEANY_FILETYPES_PHP) + { + var = g_strdup("$i"); + contruct_len = 14; + } + else + { + var = g_strdup("i"); + contruct_len = 12; + } + construct = g_strdup_printf("(%s%s = 0; %s < ; %s++)%s{%s%s%s}%s", + (doc_list[idx].file_type->id == GEANY_FILETYPES_CPP) ? "int " : "", + var, var, var, eol, eol, space, eol, eol); + + // add 4 characters because of "int " in C++ mode + contruct_len += (doc_list[idx].file_type->id == GEANY_FILETYPES_CPP) ? 4 : 0; + + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) construct); + sci_goto_pos(sci, pos + contruct_len, TRUE); + g_free(var); + } + else if (! strncmp("while", buf + 10, 5)) + { + if (! isspace(*(buf + 9))) + return FALSE; + + construct = g_strdup_printf("()%s{%s%s%s}%s", eol, eol, space, eol, eol); + + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) construct); + sci_goto_pos(sci, pos + 1, TRUE); + } + else if (! strncmp("do", buf + 13, 2)) + { + if (! isspace(*(buf + 12))) + return FALSE; + + construct = g_strdup_printf("%s{%s%s%s}%swhile ();%s", eol, eol, space, eol, eol, eol); + + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) construct); + sci_goto_pos(sci, pos + 3 + space_len + (2 * strlen(indent)), TRUE); + } + else if (! strncmp("try", buf + 12, 3)) + { + if (! isspace(*(buf + 11))) + return FALSE; + + construct = g_strdup_printf("%s{%s%s%s}%scatch ()%s{%s%s%s}%s", + eol, eol, space, eol, eol, eol, eol, space, eol, eol); + + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) construct); + sci_goto_pos(sci, pos + 3 + space_len + (2 * strlen(indent)), TRUE); + } + else if (! strncmp("switch", buf + 9, 6)) + { + if (! isspace(*(buf + 8))) + return FALSE; + + construct = g_strdup_printf("()%s{%s%scase : break;%s%sdefault: %s}%s", + eol, eol, space, eol, space, eol, eol); + + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) construct); + sci_goto_pos(sci, pos + 1, TRUE); + } + else if (doc_list[idx].file_type->id == GEANY_FILETYPES_FERITE && ! strncmp("iferr", buf + 10, 5)) + { + if (! isspace(*(buf + 9))) + return FALSE; + + construct = g_strdup_printf("%s{%s%s%s}%sfix%s{%s%s%s}%s", + eol, eol, space, eol, eol, eol, eol, space, eol, eol); + + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) construct); + sci_goto_pos(sci, pos + 3 + space_len + (2 * strlen(indent)), TRUE); + } + else if (doc_list[idx].file_type->id == GEANY_FILETYPES_FERITE && ! strncmp("monitor", buf + 8, 7)) + { + if (! isspace(*(buf + 7))) + return FALSE; + + construct = g_strdup_printf("%s{%s%s%s}%shandle%s{%s%s%s}%s", + eol, eol, space, eol, eol, eol, eol, space, eol, eol); + + SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) construct); + sci_goto_pos(sci, pos + 3 + space_len + (2 * strlen(indent)), TRUE); + } + result = (construct != NULL); + if (result) + { + sci_insert_text(sci, pos, " "); // prefix all constructs with a space + } + g_free(construct); + return result; +} + + +static gboolean at_eol(ScintillaObject *sci, gint pos) +{ + gint line = sci_get_line_from_position(sci, pos); + + return (pos == sci_get_line_end_position(sci, line)); +} + + +gboolean editor_auto_forif(gint idx, gint pos) +{ + gboolean result; + gchar buf[16]; + gchar *eol; + gchar *space; + gint lexer, style; + gint i; + ScintillaObject *sci; + + if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return FALSE; + + // only for C, C++, D, Ferite, Java, JavaScript, Perl and PHP + if (doc_list[idx].file_type->id != GEANY_FILETYPES_PHP && + doc_list[idx].file_type->id != GEANY_FILETYPES_C && + doc_list[idx].file_type->id != GEANY_FILETYPES_D && + doc_list[idx].file_type->id != GEANY_FILETYPES_CPP && + doc_list[idx].file_type->id != GEANY_FILETYPES_PERL && + doc_list[idx].file_type->id != GEANY_FILETYPES_JAVA && + doc_list[idx].file_type->id != GEANY_FILETYPES_JS && + doc_list[idx].file_type->id != GEANY_FILETYPES_FERITE) + return FALSE; + + sci = doc_list[idx].sci; + // return if we are editing an existing line (chars on right of cursor) + if (! at_eol(sci, pos)) + return FALSE; + + lexer = SSM(sci, SCI_GETLEXER, 0, 0); + style = SSM(sci, SCI_GETSTYLEAT, pos - 2, 0); + // return, if we are in a comment + if (is_comment(lexer, style)) + return FALSE; + // never auto complete in a PHP file outside of the <? ?> tags + if (lexer == SCLEX_HTML && ! (style >= SCE_HPHP_DEFAULT && style <= SCE_HPHP_OPERATOR)) + return FALSE; + + sci_get_text_range(sci, pos - 15, pos, buf); + if (sizeof(buf) != strlen(buf) + 1) + return FALSE; // not enough chars in document + + /* check that the chars before the current word are only whitespace (on this line). + * this prevents completion of '} while ' */ + i = 14; // index before \0 char + while (i >= 0 && isalpha(buf[i])) i--; // find pos before keyword + while (i >= 0 && buf[i] != '\n' && buf[i] != '\r') // we want to stay in this line('\n' check) + { + if (! isspace(buf[i])) + { + return FALSE; + } + i--; + } + + // get the indentation + if (doc_list[idx].use_auto_indention) get_indent(sci, pos, TRUE); + eol = g_strconcat(utils_get_eol_char(idx), indent, NULL); + + // get the whitespace for additional indentation + space = utils_get_whitespace(app->pref_editor_tab_width, FALSE); + + sci_start_undo_action(sci); // needed while we insert a space separately from construct + result = complete_constructs(idx, pos, buf, space, eol); + sci_end_undo_action(sci); + + utils_free_pointers(eol, space, NULL); + return result; +} + + +void editor_show_macro_list(ScintillaObject *sci) +{ + GString *words; + + if (sci == NULL) return; + + words = symbols_get_macro_list(); + if (words == NULL) return; + + SSM(sci, SCI_USERLISTSHOW, 1, (sptr_t) words->str); + g_string_free(words, TRUE); +} + + +/** + * (stolen from anjuta and heavily modified) + * This routine will auto complete XML or HTML tags that are still open by closing them + * @param ch The character we are dealing with, currently only works with the '>' character + * @return True if handled, false otherwise + */ +static gboolean handle_xml(ScintillaObject *sci, gchar ch, gint idx) +{ + gint lexer = SSM(sci, SCI_GETLEXER, 0, 0); + gint pos, min; + gchar *str_found, sel[512]; + + // If the user has turned us off, quit now. + // This may make sense only in certain languages + if (! app->pref_editor_auto_close_xml_tags || (lexer != SCLEX_HTML && lexer != SCLEX_XML)) + return FALSE; + + pos = sci_get_current_position(sci); + + // return if we are in PHP but not in a string or outside of <? ?> tags + if (doc_list[idx].file_type->id == GEANY_FILETYPES_PHP) + { + gint style = sci_get_style_at(sci, pos); + if (style != SCE_HPHP_SIMPLESTRING && style != SCE_HPHP_HSTRING && + style <= SCE_HPHP_OPERATOR && style >= SCE_HPHP_DEFAULT) + return FALSE; + } + + // if ch is /, check for </, else quit + if (ch == '/' && sci_get_char_at(sci, pos - 2) != '<') + return FALSE; + + // Grab the last 512 characters or so + min = pos - (sizeof(sel) - 1); + if (min < 0) min = 0; + + if (pos - min < 3) + return FALSE; // Smallest tag is 3 characters e.g. <p> + + sci_get_text_range(sci, min, pos, sel); + sel[sizeof(sel) - 1] = '\0'; + + if (ch == '>' && sel[pos - min - 2] == '/') + // User typed something like "<br/>" + return FALSE; + + if (ch == '/') + str_found = utils_find_open_xml_tag(sel, pos - min, TRUE); + else + str_found = utils_find_open_xml_tag(sel, pos - min, FALSE); + + // when found string is something like br, img or another short tag, quit + if (utils_str_equal(str_found, "br") + || utils_str_equal(str_found, "img") + || utils_str_equal(str_found, "base") + || utils_str_equal(str_found, "basefont") // < or not < + || utils_str_equal(str_found, "frame") + || utils_str_equal(str_found, "input") + || utils_str_equal(str_found, "link") + || utils_str_equal(str_found, "area") + || utils_str_equal(str_found, "meta")) + { + return FALSE; + } + + if (strlen(str_found) > 0) + { + gchar *to_insert; + if (ch == '/') + to_insert = g_strconcat(str_found, ">", NULL); + else + to_insert = g_strconcat("</", str_found, ">", NULL); + sci_start_undo_action(sci); + sci_replace_sel(sci, to_insert); + if (ch == '>') + { + SSM(sci, SCI_SETSEL, pos, pos); + if (utils_str_equal(str_found, "table")) editor_auto_table(sci, pos); + } + sci_end_undo_action(sci); + g_free(to_insert); + g_free(str_found); + return TRUE; + } + + + g_free(str_found); + return FALSE; +} + + +void editor_auto_table(ScintillaObject *sci, gint pos) +{ + gchar *table; + gint indent_pos; + + if (SSM(sci, SCI_GETLEXER, 0, 0) != SCLEX_HTML) return; + + get_indent(sci, pos, TRUE); + indent_pos = sci_get_line_indent_position(sci, sci_get_line_from_position(sci, pos)); + if ((pos - 7) != indent_pos) // 7 == strlen("<table>") + { + gint i, x; + x = strlen(indent); + // find the start of the <table tag + i = 1; + while (i <= pos && sci_get_char_at(sci, pos - i) != '<') i++; + // add all non whitespace before the tag to the indent string + while ((pos - i) != indent_pos) + { + indent[x++] = ' '; + i++; + } + indent[x] = '\0'; + } + + table = g_strconcat("\n", indent, " <tr>\n", indent, " <td>\n", indent, " </td>\n", + indent, " </tr>\n", indent, NULL); + sci_insert_text(sci, pos, table); + g_free(table); +} + + +static void real_comment_multiline(gint idx, gint line_start, gint last_line) +{ + gchar *eol, *str_begin, *str_end; + gint line_len; + + if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return; + + eol = utils_get_eol_char(idx); + str_begin = g_strdup_printf("%s%s", doc_list[idx].file_type->comment_open, eol); + str_end = g_strdup_printf("%s%s", doc_list[idx].file_type->comment_close, eol); + + // insert the comment strings + sci_insert_text(doc_list[idx].sci, line_start, str_begin); + line_len = sci_get_position_from_line(doc_list[idx].sci, last_line + 2); + sci_insert_text(doc_list[idx].sci, line_len, str_end); + + g_free(str_begin); + g_free(str_end); +} + + +static void real_uncomment_multiline(gint idx) +{ + // find the beginning of the multi line comment + gint pos, line, len, x; + gchar *linebuf; + + if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return; + + // remove comment open chars + pos = document_find_text(idx, doc_list[idx].file_type->comment_open, 0, TRUE, FALSE); + SSM(doc_list[idx].sci, SCI_DELETEBACK, 0, 0); + + // check whether the line is empty and can be deleted + line = sci_get_line_from_position(doc_list[idx].sci, pos); + len = sci_get_line_length(doc_list[idx].sci, line); + linebuf = sci_get_line(doc_list[idx].sci, line); + x = 0; + while (linebuf[x] != '\0' && isspace(linebuf[x])) x++; + if (x == len) SSM(doc_list[idx].sci, SCI_LINEDELETE, 0, 0); + g_free(linebuf); + + // remove comment close chars + pos = document_find_text(idx, doc_list[idx].file_type->comment_close, 0, FALSE, FALSE); + SSM(doc_list[idx].sci, SCI_DELETEBACK, 0, 0); + + // check whether the line is empty and can be deleted + line = sci_get_line_from_position(doc_list[idx].sci, pos); + len = sci_get_line_length(doc_list[idx].sci, line); + linebuf = sci_get_line(doc_list[idx].sci, line); + x = 0; + while (linebuf[x] != '\0' && isspace(linebuf[x])) x++; + if (x == len) SSM(doc_list[idx].sci, SCI_LINEDELETE, 0, 0); + g_free(linebuf); +} + + +void editor_do_uncomment(gint idx, gint line) +{ + gint first_line, last_line; + gint x, i, line_start, line_len; + gint sel_start, sel_end; + gsize co_len; + gchar sel[256], *co, *cc; + gboolean break_loop = FALSE, single_line = FALSE; + filetype *ft; + + if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return; + + if (line < 0) + { // use selection or current line + sel_start = sci_get_selection_start(doc_list[idx].sci); + sel_end = sci_get_selection_end(doc_list[idx].sci); + + first_line = sci_get_line_from_position(doc_list[idx].sci, sel_start); + // Find the last line with chars selected (not EOL char) + last_line = sci_get_line_from_position(doc_list[idx].sci, sel_end - 1); + last_line = MAX(first_line, last_line); + } + else + { + first_line = last_line = line; + sel_start = sel_end = sci_get_position_from_line(doc_list[idx].sci, line); + } + + ft = doc_list[idx].file_type; + + // detection of HTML vs PHP code, if non-PHP set filetype to XML + line_start = sci_get_position_from_line(doc_list[idx].sci, first_line); + if (ft->id == GEANY_FILETYPES_PHP) + { + if (sci_get_style_at(doc_list[idx].sci, line_start) < 118 || + sci_get_style_at(doc_list[idx].sci, line_start) > 127) + ft = filetypes[GEANY_FILETYPES_XML]; + } + + co = ft->comment_open; + cc = ft->comment_close; + if (co == NULL) return; + + co_len = strlen(co); + if (co_len == 0) return; + + SSM(doc_list[idx].sci, SCI_BEGINUNDOACTION, 0, 0); + + for (i = first_line; (i <= last_line) && (! break_loop); i++) + { + gint buf_len; + + line_start = sci_get_position_from_line(doc_list[idx].sci, i); + line_len = sci_get_line_length(doc_list[idx].sci, i); + x = 0; + + buf_len = MIN((gint)sizeof(sel) - 1, line_len - 1); + if (buf_len <= 0) + continue; + sci_get_text_range(doc_list[idx].sci, line_start, line_start + buf_len, sel); + sel[buf_len] = '\0'; + + while (isspace(sel[x])) x++; + + // to skip blank lines + if (x < line_len && sel[x] != '\0') + { + // use single line comment + if (cc == NULL || strlen(cc) == 0) + { + guint j; + + single_line = TRUE; + + switch (co_len) + { + case 1: if (sel[x] != co[0]) continue; break; + case 2: if (sel[x] != co[0] || sel[x+1] != co[1]) continue; break; + case 3: if (sel[x] != co[0] || sel[x+1] != co[1] || sel[x+2] != co[2]) + continue; break; + default: continue; + } + + SSM(doc_list[idx].sci, SCI_GOTOPOS, line_start + x + co_len, 0); + for (j = 0; j < co_len; j++) SSM(doc_list[idx].sci, SCI_DELETEBACK, 0, 0); + } + // use multi line comment + else + { + gint style_comment; + gint lexer = SSM(doc_list[idx].sci, SCI_GETLEXER, 0, 0); + + // process only lines which are already comments + switch (lexer) + { // I will list only those lexers which support multi line comments + case SCLEX_XML: + case SCLEX_HTML: + { + if (sci_get_style_at(doc_list[idx].sci, line_start) >= 118 && + sci_get_style_at(doc_list[idx].sci, line_start) <= 127) + style_comment = SCE_HPHP_COMMENT; + else style_comment = SCE_H_COMMENT; + break; + } + case SCLEX_CSS: style_comment = SCE_CSS_COMMENT; break; + case SCLEX_SQL: style_comment = SCE_SQL_COMMENT; break; + case SCLEX_CAML: style_comment = SCE_CAML_COMMENT; break; + case SCLEX_D: style_comment = SCE_D_COMMENT; break; + default: style_comment = SCE_C_COMMENT; + } + if (sci_get_style_at(doc_list[idx].sci, line_start + x) == style_comment) + { + real_uncomment_multiline(idx); + } + + // break because we are already on the last line + break_loop = TRUE; + break; + } + } + } + SSM(doc_list[idx].sci, SCI_ENDUNDOACTION, 0, 0); + + // restore selection if there is one + if (sel_start < sel_end) + { + if (single_line) + { + sci_set_selection_start(doc_list[idx].sci, sel_start - co_len); + sci_set_selection_end(doc_list[idx].sci, sel_end - ((i - first_line) * co_len)); + } + else + { + gint eol_len = (sci_get_eol_mode(doc_list[idx].sci) == SC_EOL_CRLF) ? 2 : 1; + sci_set_selection_start(doc_list[idx].sci, sel_start - co_len - eol_len); + sci_set_selection_end(doc_list[idx].sci, sel_end - co_len - eol_len); + } + } +} + + +void editor_do_comment_toggle(gint idx) +{ + gint first_line, last_line; + gint x, i, line_start, line_len; + gint sel_start, sel_end, co_len; + gint count_commented = 0, count_uncommented = 0; + gchar sel[256], *co, *cc; + gboolean break_loop = FALSE, single_line = FALSE; + gboolean first_line_was_comment = FALSE; + filetype *ft; + + if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return; + + sel_start = sci_get_selection_start(doc_list[idx].sci); + sel_end = sci_get_selection_end(doc_list[idx].sci); + + ft = doc_list[idx].file_type; + + first_line = sci_get_line_from_position(doc_list[idx].sci, + sci_get_selection_start(doc_list[idx].sci)); + // Find the last line with chars selected (not EOL char) + last_line = sci_get_line_from_position(doc_list[idx].sci, + sci_get_selection_end(doc_list[idx].sci) - 1); + last_line = MAX(first_line, last_line); + + // detection of HTML vs PHP code, if non-PHP set filetype to XML + line_start = sci_get_position_from_line(doc_list[idx].sci, first_line); + if (ft->id == GEANY_FILETYPES_PHP) + { + if (sci_get_style_at(doc_list[idx].sci, line_start) < 118 || + sci_get_style_at(doc_list[idx].sci, line_start) > 127) + ft = filetypes[GEANY_FILETYPES_XML]; + } + + co = ft->comment_open; + cc = ft->comment_close; + if (co == NULL) return; + + co_len = strlen(co); + if (co_len == 0) return; + + SSM(doc_list[idx].sci, SCI_BEGINUNDOACTION, 0, 0); + + for (i = first_line; (i <= last_line) && (! break_loop); i++) + { + gint buf_len; + + line_start = sci_get_position_from_line(doc_list[idx].sci, i); + line_len = sci_get_line_length(doc_list[idx].sci, i); + x = 0; + + buf_len = MIN((gint)sizeof(sel) - 1, line_len - 1); + if (buf_len <= 0) + continue; + sci_get_text_range(doc_list[idx].sci, line_start, line_start + buf_len, sel); + sel[buf_len] = '\0'; + + while (isspace(sel[x])) x++; + + // to skip blank lines + if (x < line_len && sel[x] != '\0') + { + // use single line comment + if (cc == NULL || strlen(cc) == 0) + { + gboolean do_continue = FALSE; + single_line = TRUE; + + switch (co_len) + { + case 1: + if (sel[x] == co[0]) + { + do_continue = TRUE; + editor_do_uncomment(idx, i); + count_uncommented++; + } + break; + case 2: + if (sel[x] == co[0] && sel[x+1] == co[1]) + { + do_continue = TRUE; + editor_do_uncomment(idx, i); + count_uncommented++; + } + break; + case 3: + if (sel[x] == co[0] && sel[x+1] == co[1] && sel[x+2] == co[2]) + { + do_continue = TRUE; + editor_do_uncomment(idx, i); + count_uncommented++; + } + break; + default: return; + } + if (do_continue && i == first_line) first_line_was_comment = TRUE; + if (do_continue) continue; + + // we are still here, so the above lines were not already comments, so comment it + editor_do_comment(idx, i, FALSE); + count_commented++; + } + // use multi line comment + else + { + gint style_comment; + gint lexer = SSM(doc_list[idx].sci, SCI_GETLEXER, 0, 0); + + // skip lines which are already comments + switch (lexer) + { // I will list only those lexers which support multi line comments + case SCLEX_XML: + case SCLEX_HTML: + { + if (sci_get_style_at(doc_list[idx].sci, line_start) >= 118 && + sci_get_style_at(doc_list[idx].sci, line_start) <= 127) + style_comment = SCE_HPHP_COMMENT; + else style_comment = SCE_H_COMMENT; + break; + } + case SCLEX_CSS: style_comment = SCE_CSS_COMMENT; break; + case SCLEX_SQL: style_comment = SCE_SQL_COMMENT; break; + case SCLEX_CAML: style_comment = SCE_CAML_COMMENT; break; + case SCLEX_D: style_comment = SCE_D_COMMENT; break; + default: style_comment = SCE_C_COMMENT; + } + if (sci_get_style_at(doc_list[idx].sci, line_start + x) == style_comment) + { + real_uncomment_multiline(idx); + count_uncommented++; + } + else + { + real_comment_multiline(idx, line_start, last_line); + count_commented++; + } + + // break because we are already on the last line + break_loop = TRUE; + break; + } + } + } + + SSM(doc_list[idx].sci, SCI_ENDUNDOACTION, 0, 0); + + // restore selection if there is one + if (sel_start < sel_end) + { + if (single_line) + { + gint a = (first_line_was_comment) ? - co_len : co_len; + + // don't modify sel_start when the selection starts within indentation + get_indent(doc_list[idx].sci, sel_start, TRUE); + if ((sel_start - line_start) <= (gint) strlen(indent)) + a = 0; + + sci_set_selection_start(doc_list[idx].sci, sel_start + a); + sci_set_selection_end(doc_list[idx].sci, sel_end + + (count_commented * co_len) - (count_uncommented * co_len)); + } + else + { + gint eol_len = (sci_get_eol_mode(doc_list[idx].sci) == SC_EOL_CRLF) ? 2 : 1; + if (count_uncommented > 0) + { + sci_set_selection_start(doc_list[idx].sci, sel_start - co_len - eol_len); + sci_set_selection_end(doc_list[idx].sci, sel_end - co_len - eol_len); + } + else + { + sci_set_selection_start(doc_list[idx].sci, sel_start + co_len + eol_len); + sci_set_selection_end(doc_list[idx].sci, sel_end + co_len + eol_len); + } + } + } + else if (count_uncommented > 0) + { + gint eol_len = (sci_get_eol_mode(doc_list[idx].sci) == SC_EOL_CRLF) ? 2 : 1; + sci_set_current_position(doc_list[idx].sci, sel_start - co_len - eol_len, TRUE); + } +} + + +void editor_do_comment(gint idx, gint line, gboolean allow_empty_lines) +{ + gint first_line, last_line; + gint x, i, line_start, line_len; + gint sel_start, sel_end, co_len; + gchar sel[256], *co, *cc; + gboolean break_loop = FALSE, single_line = FALSE; + filetype *ft; + + if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return; + + if (line < 0) + { // use selection or current line + sel_start = sci_get_selection_start(doc_list[idx].sci); + sel_end = sci_get_selection_end(doc_list[idx].sci); + + first_line = sci_get_line_from_position(doc_list[idx].sci, sel_start); + // Find the last line with chars selected (not EOL char) + last_line = sci_get_line_from_position(doc_list[idx].sci, sel_end - 1); + last_line = MAX(first_line, last_line); + } + else + { + first_line = last_line = line; + sel_start = sel_end = sci_get_position_from_line(doc_list[idx].sci, line); + } + + ft = doc_list[idx].file_type; + + // detection of HTML vs PHP code, if non-PHP set filetype to XML + line_start = sci_get_position_from_line(doc_list[idx].sci, first_line); + if (ft->id == GEANY_FILETYPES_PHP) + { + if (sci_get_style_at(doc_list[idx].sci, line_start) < 118 || + sci_get_style_at(doc_list[idx].sci, line_start) > 127) + ft = filetypes[GEANY_FILETYPES_XML]; + } + + co = ft->comment_open; + cc = ft->comment_close; + if (co == NULL) return; + + co_len = strlen(co); + if (co_len == 0) return; + + SSM(doc_list[idx].sci, SCI_BEGINUNDOACTION, 0, 0); + + for (i = first_line; (i <= last_line) && (! break_loop); i++) + { + gint buf_len; + + line_start = sci_get_position_from_line(doc_list[idx].sci, i); + line_len = sci_get_line_length(doc_list[idx].sci, i); + x = 0; + + buf_len = MIN((gint)sizeof(sel) - 1, line_len - 1); + if (buf_len <= 0) + continue; + sci_get_text_range(doc_list[idx].sci, line_start, line_start + buf_len, sel); + sel[buf_len] = '\0'; + + while (isspace(sel[x])) x++; + + // to skip blank lines + if (allow_empty_lines || (x < line_len && sel[x] != '\0')) + { + // use single line comment + if (cc == NULL || strlen(cc) == 0) + { + single_line = TRUE; + + if (ft->comment_use_indent) + sci_insert_text(doc_list[idx].sci, line_start + x, co); + else + sci_insert_text(doc_list[idx].sci, line_start, co); + } + // use multi line comment + else + { + gint style_comment; + gint lexer = SSM(doc_list[idx].sci, SCI_GETLEXER, 0, 0); + + // skip lines which are already comments + switch (lexer) + { // I will list only those lexers which support multi line comments + case SCLEX_XML: + case SCLEX_HTML: + { + if (sci_get_style_at(doc_list[idx].sci, line_start) >= 118 && + sci_get_style_at(doc_list[idx].sci, line_start) <= 127) + style_comment = SCE_HPHP_COMMENT; + else style_comment = SCE_H_COMMENT; + break; + } + case SCLEX_CSS: style_comment = SCE_CSS_COMMENT; break; + case SCLEX_SQL: style_comment = SCE_SQL_COMMENT; break; + case SCLEX_CAML: style_comment = SCE_CAML_COMMENT; break; + case SCLEX_D: style_comment = SCE_D_COMMENT; break; + default: style_comment = SCE_C_COMMENT; + } + if (sci_get_style_at(doc_list[idx].sci, line_start + x) == style_comment) continue; + + real_comment_multiline(idx, line_start, last_line); + + // break because we are already on the last line + break_loop = TRUE; + break; + } + } + } + SSM(doc_list[idx].sci, SCI_ENDUNDOACTION, 0, 0); + + // restore selection if there is one + if (sel_start < sel_end) + { + if (single_line) + { + sci_set_selection_start(doc_list[idx].sci, sel_start + co_len); + sci_set_selection_end(doc_list[idx].sci, sel_end + ((i - first_line) * co_len)); + } + else + { + gint eol_len = (sci_get_eol_mode(doc_list[idx].sci) == SC_EOL_CRLF) ? 2 : 1; + sci_set_selection_start(doc_list[idx].sci, sel_start + co_len + eol_len); + sci_set_selection_end(doc_list[idx].sci, sel_end + co_len + eol_len); + } + } +} + + +void editor_highlight_braces(ScintillaObject *sci, gint cur_pos) +{ + gint brace_pos = cur_pos - 1; + gint end_pos; + + if (! utils_isbrace(sci_get_char_at(sci, brace_pos))) + { + brace_pos++; + if (! utils_isbrace(sci_get_char_at(sci, brace_pos))) + { + SSM(sci, SCI_BRACEBADLIGHT, -1, 0); + return; + } + } + end_pos = SSM(sci, SCI_BRACEMATCH, brace_pos, 0); + + if (end_pos >= 0) + SSM(sci, SCI_BRACEHIGHLIGHT, brace_pos, end_pos); + else + SSM(sci, SCI_BRACEBADLIGHT, brace_pos, 0); +} + + +static gboolean is_doc_comment_char(gchar c, gint lexer) +{ + if (c == '*' && (lexer = SCLEX_HTML || lexer == SCLEX_CPP)) + return TRUE; + else if ((c == '*' || c == '+') && lexer == SCLEX_D) + return TRUE; + else + return FALSE; +} + + +static void auto_multiline(ScintillaObject *sci, gint pos) +{ + gint style = SSM(sci, SCI_GETSTYLEAT, pos - 2, 0); + gint lexer = SSM(sci, SCI_GETLEXER, 0, 0); + gint i; + + if ((lexer == SCLEX_CPP && (style == SCE_C_COMMENT || style == SCE_C_COMMENTDOC)) || + (lexer == SCLEX_HTML && style == SCE_HPHP_COMMENT) || + (lexer == SCLEX_D && (style == SCE_D_COMMENT || + style == SCE_D_COMMENTDOC || + style == SCE_D_COMMENTNESTED))) + { + gchar *previous_line = sci_get_line(sci, sci_get_line_from_position(sci, pos - 2)); + gchar *continuation = "*"; // the type of comment, '*' (C/C++/Java), '+' and the others (D) + gchar *whitespace = ""; // to hold whitespace if needed + gchar *result; + gint len = strlen(previous_line); + + // find and stop at end of multi line comment + i = len - 1; + while (i >= 0 && isspace(previous_line[i])) i--; + if (i >= 1 && is_doc_comment_char(previous_line[i - 1], lexer) && previous_line[i] == '/') + { + gint cur_line = sci_get_current_line(sci, -1); + gint indent_pos = sci_get_line_indent_position(sci, cur_line); + gint indent_len = sci_get_col_from_position(sci, indent_pos); + + /* if there is one too many spaces, delete the last space, + * to return to the indent used before the multiline comment was started. */ + if (indent_len % app->pref_editor_tab_width == 1) + SSM(sci, SCI_DELETEBACKNOTLINE, 0, 0); // remove whitespace indent + g_free(previous_line); + return; + } + // check whether we are on the second line of multi line comment + i = 0; + while (i < len && isspace(previous_line[i])) i++; // get to start of the line + + if (i + 1 < len && + previous_line[i] == '/' && is_doc_comment_char(previous_line[i + 1], lexer)) + { // we are on the second line of a multi line comment, so we have to insert white space + whitespace = " "; + } + + if (style == SCE_D_COMMENTNESTED) continuation = "+"; // for nested comments in D + + result = g_strconcat(whitespace, continuation, " ", NULL); + sci_add_text(sci, result); + g_free(result); + + g_free(previous_line); + } +} + + +/* Checks whether the given style is a comment or string for the given lexer. + * It doesn't handle LEX_HTML, this should be done by the caller. + * Returns true if the style is a comment, FALSE otherwise. + */ +static gboolean is_comment(gint lexer, gint style) +{ + gboolean result = FALSE; + + switch (lexer) + { + case SCLEX_CPP: + case SCLEX_PASCAL: + { + if (style == SCE_C_COMMENT || + style == SCE_C_COMMENTLINE || + style == SCE_C_COMMENTDOC || + style == SCE_C_COMMENTLINEDOC || + style == SCE_C_CHARACTER || + style == SCE_C_PREPROCESSOR || + style == SCE_C_STRING) + result = TRUE; + break; + } + case SCLEX_D: + { + if (style == SCE_D_COMMENT || + style == SCE_D_COMMENTLINE || + style == SCE_D_COMMENTDOC || + style == SCE_D_COMMENTLINEDOC || + style == SCE_D_COMMENTNESTED || + style == SCE_D_CHARACTER || + style == SCE_D_STRING) + result = TRUE; + break; + } + case SCLEX_PYTHON: + { + if (style == SCE_P_COMMENTLINE || + style == SCE_P_COMMENTBLOCK || + style == SCE_P_STRING) + result = TRUE; + break; + } + case SCLEX_F77: + { + if (style == SCE_F_COMMENT || + style == SCE_F_STRING1 || + style == SCE_F_STRING2) + result = TRUE; + break; + } + case SCLEX_PERL: + { + if (style == SCE_PL_COMMENTLINE || + style == SCE_PL_STRING) + result = TRUE; + break; + } + case SCLEX_PROPERTIES: + { + if (style == SCE_PROPS_COMMENT) + result = TRUE; + break; + } + case SCLEX_LATEX: + { + if (style == SCE_L_COMMENT) + result = TRUE; + break; + } + case SCLEX_MAKEFILE: + { + if (style == SCE_MAKE_COMMENT) + result = TRUE; + break; + } + case SCLEX_RUBY: + { + if (style == SCE_RB_COMMENTLINE || + style == SCE_RB_STRING) + result = TRUE; + break; + } + case SCLEX_BASH: + { + if (style == SCE_SH_COMMENTLINE || + style == SCE_SH_STRING) + result = TRUE; + break; + } + case SCLEX_SQL: + { + if (style == SCE_SQL_COMMENT || + style == SCE_SQL_COMMENTLINE || + style == SCE_SQL_COMMENTDOC || + style == SCE_SQL_STRING) + result = TRUE; + break; + } + case SCLEX_TCL: + { + if (style == SCE_TCL_COMMENT || + style == SCE_TCL_COMMENTLINE || + style == SCE_TCL_IN_QUOTE) + result = TRUE; + break; + } + case SCLEX_LUA: + { + if (style == SCE_LUA_COMMENT || + style == SCE_LUA_COMMENTLINE || + style == SCE_LUA_COMMENTDOC || + style == SCE_LUA_LITERALSTRING || + style == SCE_LUA_CHARACTER || + style == SCE_LUA_STRING) + result = TRUE; + break; + } + case SCLEX_HASKELL: + { + if (style == SCE_HA_COMMENTLINE || + style == SCE_HA_COMMENTBLOCK || + style == SCE_HA_COMMENTBLOCK2 || + style == SCE_HA_COMMENTBLOCK3 || + style == SCE_HA_CHARACTER || + style == SCE_HA_STRING) + result = TRUE; + break; + } + case SCLEX_HTML: + { + if (style == SCE_HPHP_SIMPLESTRING || + style == SCE_HPHP_HSTRING || + style == SCE_HPHP_COMMENTLINE || + style == SCE_HPHP_COMMENT || + style == SCE_H_DOUBLESTRING || + style == SCE_H_SINGLESTRING || + style == SCE_H_CDATA || + style == SCE_H_COMMENT || + style == SCE_H_SGML_DOUBLESTRING || + style == SCE_H_SGML_SIMPLESTRING || + style == SCE_H_SGML_COMMENT) + result = TRUE; + break; + } + } + + return result; +} + + +#if 0 +gboolean editor_lexer_is_c_like(gint lexer) +{ + switch (lexer) + { + case SCLEX_CPP: + case SCLEX_D: + return TRUE; + + default: + return FALSE; + } +} +#endif + + +// Returns: -1 if lexer doesn't support type keywords +gint editor_lexer_get_type_keyword_idx(gint lexer) +{ + switch (lexer) + { + case SCLEX_CPP: + case SCLEX_D: + return 3; + + default: + return -1; + } +} + + +// inserts a three-line comment at one line above current cursor position +void editor_insert_multiline_comment(gint idx) +{ + gchar *text; + gint text_len; + gint line; + gint pos; + gboolean have_multiline_comment = FALSE; + + if (doc_list[idx].file_type->comment_close != NULL && + strlen(doc_list[idx].file_type->comment_close) > 0) + have_multiline_comment = TRUE; + + // insert three lines one line above of the current position + line = sci_get_line_from_position(doc_list[idx].sci, editor_info.click_pos); + pos = sci_get_position_from_line(doc_list[idx].sci, line); + + // use the indentation on the current line but only when comment indention is used + // and we don't have multi line comment characters + if (doc_list[idx].use_auto_indention && ! have_multiline_comment && + doc_list[idx].file_type->comment_use_indent) + { + get_indent(doc_list[idx].sci, editor_info.click_pos, TRUE); + text = g_strdup_printf("%s\n%s\n%s\n", indent, indent, indent); + text_len = strlen(text); + } + else + { + text = g_strdup("\n\n\n"); + text_len = 3; + } + sci_insert_text(doc_list[idx].sci, pos, text); + g_free(text); + + + // select the inserted lines for commenting + sci_set_selection_start(doc_list[idx].sci, pos); + sci_set_selection_end(doc_list[idx].sci, pos + text_len); + + editor_do_comment(idx, -1, TRUE); + + // set the current position to the start of the first inserted line + pos += strlen(doc_list[idx].file_type->comment_open); + + // on multi line comment jump to the next line, otherwise add the length of added indentation + if (have_multiline_comment) + pos += 1; + else + pos += strlen(indent); + + sci_set_current_position(doc_list[idx].sci, pos, TRUE); + // reset the selection + sci_set_anchor(doc_list[idx].sci, pos); +} + + +/* Scroll the view to make line appear at percent_of_view. + * line can be -1 to use the current position. */ +static void scroll_to_line(ScintillaObject *sci, gint line, gfloat percent_of_view) +{ + gint vis1, los, delta; + GtkWidget *wid = GTK_WIDGET(sci); + + if (! wid->window || ! gdk_window_is_viewable(wid->window)) + return; // prevent gdk_window_scroll warning + + if (line == -1) + line = sci_get_current_line(sci, -1); + + // sci 'visible line' != doc line number because of folding and line wrapping + /* calling SCI_VISIBLEFROMDOCLINE for line is more accurate than calling + * SCI_DOCLINEFROMVISIBLE for vis1. */ + line = SSM(sci, SCI_VISIBLEFROMDOCLINE, line, 0); + vis1 = SSM(sci, SCI_GETFIRSTVISIBLELINE, 0, 0); + los = SSM(sci, SCI_LINESONSCREEN, 0, 0); + delta = (line - vis1) - los * percent_of_view; + sci_scroll_lines(sci, delta); + //sci_scroll_caret(sci); // ensure visible (maybe not needed now) +} + + +void editor_insert_alternative_whitespace(ScintillaObject *sci) +{ + // creates and inserts one tabulator sign or whitespace of the amount of the tab width + gchar *text = utils_get_whitespace(app->pref_editor_tab_width, TRUE); + sci_add_text(sci, text); + g_free(text); +} + + +void editor_select_word(ScintillaObject *sci) +{ + gint pos; + gint start; + gint end; + + g_return_if_fail(sci != NULL); + + pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + start = SSM(sci, SCI_WORDSTARTPOSITION, pos, TRUE); + end = SSM(sci, SCI_WORDENDPOSITION, pos, TRUE); + + if (start == end) // caret in whitespaces sequence + { + // look forward but reverse the selection direction, + // so the caret end up stay as near as the original position. + end = SSM(sci, SCI_WORDENDPOSITION, pos, FALSE); + start = SSM(sci, SCI_WORDENDPOSITION, end, TRUE); + if (start == end) + return; + } + + SSM(sci, SCI_SETSEL, start, end); +} + +
Copied: trunk/src/editor.h (from rev 1583, trunk/src/sci_cb.h) =================================================================== --- trunk/src/editor.h (rev 0) +++ trunk/src/editor.h 2007-05-28 16:07:30 UTC (rev 1584) @@ -0,0 +1,93 @@ +/* + * editor.h - this file is part of Geany, a fast and lightweight IDE + * + * Copyright 2005-2007 Enrico Tröger enrico.troeger@uvena.de + * Copyright 2006-2007 Nick Treleaven nick.treleaven@btinternet.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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef GEANY_SCI_CB_H +#define GEANY_SCI_CB_H 1 + +#ifndef PLAT_GTK +# define PLAT_GTK 1 // needed for ScintillaWidget.h +#endif + +#include "Scintilla.h" +#include "ScintillaWidget.h" + +// Note: Avoid using SSM in files not related to scintilla, use sciwrappers.h instead. +#define SSM(s, m, w, l) scintilla_send_message(s, m, w, l) + + +typedef struct +{ + gchar *current_word; // holds word under the mouse or keyboard cursor + gint click_pos; // text position where the mouse was clicked +} EditorInfo; + +extern EditorInfo editor_info; + + +gboolean +on_editor_button_press_event (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data); + +// callback func called by all editors when a signal arises +void on_editor_notification(GtkWidget* editor, gint scn, gpointer lscn, gpointer user_data); + +gboolean editor_start_auto_complete(gint idx, gint pos, gboolean force); + +void editor_close_block(gint idx, gint pos); + +gboolean editor_auto_forif(gint idx, gint pos); + +void editor_auto_latex(gint idx, gint pos); + +void editor_show_macro_list(ScintillaObject *sci); + +/* Reads the word at given cursor position and writes it into the given buffer. The buffer will be + * NULL terminated in any case, even when the word is truncated because wordlen is too small. + * position can be -1, then the current position is used. + * wc are the wordchars to use, if NULL, GEANY_WORDCHARS will be used */ +void editor_find_current_word(ScintillaObject *sci, gint pos, gchar *word, size_t wordlen, + const gchar *wc); + +gboolean editor_show_calltip(gint idx, gint pos); + +void editor_do_comment_toggle(gint idx); + +void editor_do_comment(gint idx, gint line, gboolean allow_empty_lines); + +void editor_do_uncomment(gint idx, gint line); + +void editor_highlight_braces(ScintillaObject *sci, gint cur_pos); + +void editor_auto_table(ScintillaObject *sci, gint pos); + +gboolean editor_lexer_is_c_like(gint lexer); + +gint editor_lexer_get_type_keyword_idx(gint lexer); + +void editor_insert_multiline_comment(gint idx); + +void editor_select_word(ScintillaObject *sci); + +void editor_insert_alternative_whitespace(ScintillaObject *sci); + +#endif
Modified: trunk/src/highlighting.c =================================================================== --- trunk/src/highlighting.c 2007-05-28 15:28:30 UTC (rev 1583) +++ trunk/src/highlighting.c 2007-05-28 16:07:30 UTC (rev 1584) @@ -30,7 +30,7 @@ #include "SciLexer.h" #include "geany.h" #include "highlighting.h" -#include "sci_cb.h" +#include "editor.h" #include "utils.h" #include "filetypes.h" #include "symbols.h"
Modified: trunk/src/keybindings.c =================================================================== --- trunk/src/keybindings.c 2007-05-28 15:28:30 UTC (rev 1583) +++ trunk/src/keybindings.c 2007-05-28 16:07:30 UTC (rev 1584) @@ -36,7 +36,7 @@ #include "callbacks.h" #include "prefs.h" #include "msgwindow.h" -#include "sci_cb.h" +#include "editor.h" #include "sciwrappers.h" #include "build.h" #include "tools.h" @@ -617,7 +617,7 @@ gint pos = sci_get_current_position(sci);
if (app->pref_editor_auto_complete_constructs) - return sci_cb_auto_forif(idx, pos); + return editor_auto_forif(idx, pos); } } return FALSE; @@ -901,7 +901,7 @@
pos = sci_get_current_position(doc_list[idx].sci);
- sci_cb_find_current_word(doc_list[idx].sci, pos, + editor_find_current_word(doc_list[idx].sci, pos, editor_info.current_word, GEANY_MAX_WORD_LENGTH, NULL);
if (*editor_info.current_word == 0) @@ -1082,13 +1082,13 @@ on_menu_uncomment_line1_activate(NULL, NULL); break; case GEANY_KEYS_EDIT_AUTOCOMPLETE: - sci_cb_start_auto_complete(idx, sci_get_current_position(doc_list[idx].sci), TRUE); + editor_start_auto_complete(idx, sci_get_current_position(doc_list[idx].sci), TRUE); break; case GEANY_KEYS_EDIT_CALLTIP: - sci_cb_show_calltip(idx, -1); + editor_show_calltip(idx, -1); break; case GEANY_KEYS_EDIT_MACROLIST: - sci_cb_show_macro_list(doc_list[idx].sci); + editor_show_macro_list(doc_list[idx].sci); break;
case GEANY_KEYS_EDIT_SUPPRESSCOMPLETION: @@ -1106,10 +1106,10 @@ break;
case GEANY_KEYS_EDIT_SELECTWORD: - sci_cb_select_word(doc_list[idx].sci); + editor_select_word(doc_list[idx].sci); break; case GEANY_KEYS_EDIT_INSERTALTWHITESPACE: - sci_cb_insert_alternative_whitespace(doc_list[idx].sci); + editor_insert_alternative_whitespace(doc_list[idx].sci); break; case GEANY_KEYS_EDIT_INCREASEINDENT: on_menu_increase_indent1_activate(NULL, NULL);
Modified: trunk/src/main.c =================================================================== --- trunk/src/main.c 2007-05-28 15:28:30 UTC (rev 1583) +++ trunk/src/main.c 2007-05-28 16:07:30 UTC (rev 1584) @@ -52,7 +52,7 @@ #include "treeviews.h" #include "notebook.h" #include "keybindings.h" -#include "sci_cb.h" +#include "editor.h" #include "search.h" #include "build.h" #include "highlighting.h"
Modified: trunk/src/makefile.win32 =================================================================== --- trunk/src/makefile.win32 2007-05-28 15:28:30 UTC (rev 1583) +++ trunk/src/makefile.win32 2007-05-28 16:07:30 UTC (rev 1584) @@ -46,7 +46,7 @@
OBJS = treeviews.o templates.o encodings.o about.o prefs.o win32.o build.o msgwindow.o dialogs.o \ filetypes.o interface.o main.o support.o callbacks.o utils.o ui_utils.o socket.o \ - highlighting.o sci_cb.o document.o sciwrappers.o keyfile.o keybindings.o search.o notebook.o \ + highlighting.o editor.o document.o sciwrappers.o keyfile.o keybindings.o search.o notebook.o \ symbols.o tools.o project.o
.c.o:
Deleted: trunk/src/sci_cb.c =================================================================== --- trunk/src/sci_cb.c 2007-05-28 15:28:30 UTC (rev 1583) +++ trunk/src/sci_cb.c 2007-05-28 16:07:30 UTC (rev 1584) @@ -1,2296 +0,0 @@ -/* - * sci_cb.c - this file is part of Geany, a fast and lightweight IDE - * - * Copyright 2005-2007 Enrico Tröger enrico.troeger@uvena.de - * Copyright 2006-2007 Nick Treleaven nick.treleaven@btinternet.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 2 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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * $Id$ - */ - -/* - * Callbacks for the Scintilla widget (ScintillaObject). - * Most important is the sci-notify callback, handled in on_editor_notification(). - * This includes auto-indentation, comments, auto-completion, calltips, etc. - * Also some general Scintilla-related functions. - */ - -#include <ctype.h> -#include <string.h> - -#include "SciLexer.h" -#include "geany.h" - -#include "sci_cb.h" -#include "document.h" -#include "sciwrappers.h" -#include "ui_utils.h" -#include "utils.h" -#include "symbols.h" - - -static gchar current_word[GEANY_MAX_WORD_LENGTH]; // holds word under the mouse or keyboard cursor - -EditorInfo editor_info = {current_word, -1}; - -static struct -{ - gchar *text; - gboolean set; - gchar *last_word; - guint tag_index; -} calltip = {NULL, FALSE, NULL, 0}; - -static gchar indent[100]; - - -static void on_new_line_added(ScintillaObject *sci, gint idx); -static gboolean handle_xml(ScintillaObject *sci, gchar ch, gint idx); -static void get_indent(ScintillaObject *sci, gint pos, gboolean use_this_line); -static void auto_multiline(ScintillaObject *sci, gint pos); -static gboolean is_comment(gint lexer, gint style); -static void scroll_to_line(ScintillaObject *sci, gint line, gfloat percent_of_view); -static void auto_close_bracket(ScintillaObject *sci, gint pos, gchar c); - - -// calls the edit popup menu in the editor -gboolean -on_editor_button_press_event (GtkWidget *widget, - GdkEventButton *event, - gpointer user_data) -{ - gint idx = GPOINTER_TO_INT(user_data); - editor_info.click_pos = sci_get_position_from_xy(doc_list[idx].sci, event->x, event->y, FALSE); - - if (event->button == 1) - { - if (GDK_BUTTON_PRESS==event->type && app->pref_editor_disable_dnd) - { - gint ss = sci_get_selection_start(doc_list[idx].sci); - sci_set_selection_end(doc_list[idx].sci, ss); - } - return utils_check_disk_status(idx, FALSE); - } - - if (event->button == 3) - { - sci_cb_find_current_word(doc_list[idx].sci, editor_info.click_pos, - current_word, sizeof current_word, NULL); - - ui_update_popup_goto_items((current_word[0] != '\0') ? TRUE : FALSE); - ui_update_popup_copy_items(idx); - ui_update_insert_include_item(idx, 0); - gtk_menu_popup(GTK_MENU(app->popup_menu), NULL, NULL, NULL, NULL, event->button, event->time); - - return TRUE; - } - return FALSE; -} - - -typedef struct SCNotification SCNotification; - -static void on_margin_click(ScintillaObject *sci, SCNotification *nt) -{ - // left click to marker margin marks the line - if (nt->margin == 1) - { - gint line = sci_get_line_from_position(sci, nt->position); - gboolean set = sci_is_marker_set_at_line(sci, line, 1); - - //sci_marker_delete_all(doc_list[idx].sci, 1); - sci_set_marker_at_line(sci, line, ! set, 1); // toggle the marker - } - // left click on the folding margin to toggle folding state of current line - else if (nt->margin == 2 && app->pref_editor_folding) - { - gint line = SSM(sci, SCI_LINEFROMPOSITION, nt->position, 0); - - SSM(sci, SCI_TOGGLEFOLD, line, 0); - if (app->pref_editor_unfold_all_children && - SSM(sci, SCI_GETLINEVISIBLE, line + 1, 0)) - { // unfold all children of the current fold point - gint last_line = SSM(sci, SCI_GETLASTCHILD, line, -1); - gint i; - - for (i = line; i < last_line; i++) - { - if (! SSM(sci, SCI_GETLINEVISIBLE, i, 0)) - { - SSM(sci, SCI_TOGGLEFOLD, SSM(sci, SCI_GETFOLDPARENT, i, 0), 0); - } - } - } - } -} - - -static void on_update_ui(gint idx, G_GNUC_UNUSED SCNotification *nt) -{ - ScintillaObject *sci = doc_list[idx].sci; - gint pos = sci_get_current_position(sci); - - // undo / redo menu update - ui_update_popup_reundo_items(idx); - - // brace highlighting - sci_cb_highlight_braces(sci, pos); - - ui_update_statusbar(idx, pos); - - /* Visible lines are only laid out accurately once [SCN_UPDATEUI] is sent, - * so we need to only call sci_scroll_to_line here, because the document - * may have line wrapping and folding enabled. - * http://scintilla.sourceforge.net/ScintillaDoc.html#LineWrapping */ - if (doc_list[idx].scroll_percent > 0.0F) - { - scroll_to_line(sci, -1, doc_list[idx].scroll_percent); - doc_list[idx].scroll_percent = -1.0F; // disable further scrolling - } -#if 0 - /// experimental code for inverting selections - { - gint i; - for (i = SSM(sci, SCI_GETSELECTIONSTART, 0, 0); i < SSM(sci, SCI_GETSELECTIONEND, 0, 0); i++) - { - // need to get colour from getstyleat(), but how? - SSM(sci, SCI_STYLESETFORE, STYLE_DEFAULT, 0); - SSM(sci, SCI_STYLESETBACK, STYLE_DEFAULT, 0); - } - - sci_get_style_at(sci, pos); - } -#endif -} - - -static void on_char_added(gint idx, SCNotification *nt) -{ - ScintillaObject *sci = doc_list[idx].sci; - gint pos = sci_get_current_position(sci); - - switch (nt->ch) - { - case '\r': - { // simple indentation (only for CR format) - if (sci_get_eol_mode(sci) == SC_EOL_CR) - on_new_line_added(sci, idx); - break; - } - case '\n': - { // simple indentation (for CR/LF and LF format) - on_new_line_added(sci, idx); - break; - } - case '>': - case '/': - { // close xml-tags - handle_xml(sci, nt->ch, idx); - break; - } - case '(': - { // show calltips - sci_cb_show_calltip(idx, --pos); - break; - } - case ')': - { // hide calltips - if (SSM(sci, SCI_CALLTIPACTIVE, 0, 0)) - { - SSM(sci, SCI_CALLTIPCANCEL, 0, 0); - } - g_free(calltip.text); - calltip.text = NULL; - calltip.set = FALSE; - break; - } - case '[': - case '{': - { // Tex auto-closing - if (sci_get_lexer(sci) == SCLEX_LATEX) - { - auto_close_bracket(sci, pos, nt->ch); // Tex auto-closing - sci_cb_show_calltip(idx, --pos); - } - break; - } - case '}': - { // closing bracket handling - if (doc_list[idx].use_auto_indention && - app->pref_editor_indention_mode == INDENT_ADVANCED) - sci_cb_close_block(idx, pos - 1); - break; - } - default: sci_cb_start_auto_complete(idx, pos, FALSE); - } -} - - -// callback func called by all editors when a signal arises -void on_editor_notification(GtkWidget *editor, gint scn, gpointer lscn, gpointer user_data) -{ - SCNotification *nt; - ScintillaObject *sci; - gint idx; - - idx = GPOINTER_TO_INT(user_data); - sci = doc_list[idx].sci; - - nt = lscn; - switch (nt->nmhdr.code) - { - case SCN_SAVEPOINTLEFT: - { - doc_list[idx].changed = TRUE; - document_set_text_changed(idx); - break; - } - case SCN_SAVEPOINTREACHED: - { - doc_list[idx].changed = FALSE; - document_set_text_changed(idx); - break; - } - case SCN_MODIFYATTEMPTRO: - { - utils_beep(); - break; - } - case SCN_MARGINCLICK: - on_margin_click(sci, nt); - break; - - case SCN_UPDATEUI: - on_update_ui(idx, nt); - break; - - case SCN_MODIFIED: - { - if (nt->modificationType & SC_STARTACTION && ! app->ignore_callback) - { - // get notified about undo changes - document_undo_add(idx, UNDO_SCINTILLA, NULL); - } - break; - } - case SCN_CHARADDED: - on_char_added(idx, nt); - break; - - case SCN_USERLISTSELECTION: - { - if (nt->listType == 1) - { - gint pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); - SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) nt->text); - } - else if (nt->listType == 2) - { - gint start, pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); - start = pos; - while (start > 0 && sci_get_char_at(sci, --start) != '&') ; - - SSM(sci, SCI_INSERTTEXT, pos - 1, (sptr_t) nt->text); - } - break; - } - case SCN_AUTOCSELECTION: - { - // now that autocomplete is finishing, reshow calltips if they were showing - if (calltip.set) - { - gint pos = sci_get_current_position(sci); - SSM(sci, SCI_CALLTIPSHOW, pos, (sptr_t) calltip.text); - // now autocompletion has been cancelled, so do it manually - sci_set_selection_start(sci, nt->lParam); - sci_set_selection_end(sci, pos); - sci_replace_sel(sci, ""); // clear root of word - SSM(sci, SCI_INSERTTEXT, nt->lParam, (sptr_t) nt->text); - sci_goto_pos(sci, nt->lParam + strlen(nt->text), FALSE); - } - break; - } -#ifdef GEANY_DEBUG - case SCN_STYLENEEDED: - { - geany_debug("style"); - break; - } -#endif - case SCN_URIDROPPED: - { - if (nt->text != NULL) - { - document_open_file_list(nt->text, -1); - } - break; - } - case SCN_CALLTIPCLICK: - { - if (nt->position > 0) - { - switch (nt->position) - { - case 1: // up arrow - if (calltip.tag_index > 0) - calltip.tag_index--; - break; - - case 2: calltip.tag_index++; break; // down arrow - } - sci_cb_show_calltip(idx, -1); - } - break; - } - } -} - - -static void on_new_line_added(ScintillaObject *sci, gint idx) -{ - gint pos = sci_get_current_position(sci); - - // simple indentation - if (doc_list[idx].use_auto_indention) - { - get_indent(sci, pos, FALSE); - sci_add_text(sci, indent); - - if (app->pref_editor_indention_mode == INDENT_ADVANCED) - { - // add extra indentation for Python after colon - if (FILETYPE_ID(doc_list[idx].file_type) == GEANY_FILETYPES_PYTHON && - sci_get_char_at(sci, pos - 2) == ':' && - sci_get_style_at(sci, pos - 2) == SCE_P_OPERATOR) - { - // creates and inserts one tabulator sign or whitespace of the amount of the tab width - gchar *text = utils_get_whitespace(app->pref_editor_tab_width, FALSE); - sci_add_text(sci, text); - g_free(text); - } - } - } - - if (app->pref_editor_auto_complete_constructs) - { - // " * " auto completion in multiline C/C++/D/Java comments - auto_multiline(sci, pos); - - sci_cb_auto_latex(idx, pos); - } -} - - -static gboolean lexer_has_braces(ScintillaObject *sci) -{ - gint lexer = SSM(sci, SCI_GETLEXER, 0, 0); - - switch (lexer) - { - case SCLEX_CPP: - case SCLEX_D: - case SCLEX_HTML: // for PHP & JS - case SCLEX_PASCAL: // for multiline comments? - case SCLEX_BASH: - case SCLEX_PERL: - case SCLEX_TCL: - return TRUE; - default: - return FALSE; - } -} - - -// in place indentation of one tab or equivalent spaces -static void do_indent(gchar *buf, gsize len, guint *idx) -{ - guint j = *idx; - - if (app->pref_editor_use_tabs) - { - if (j < len - 1) // leave room for a \0 terminator. - buf[j++] = '\t'; - } - else - { // insert as many spaces as a tab would take - guint k; - for (k = 0; k < (guint) app->pref_editor_tab_width && k < len - 1; k++) - buf[j++] = ' '; - } - *idx = j; -} - - -/* "use_this_line" to auto-indent only if it is a real new line - * and ignore the case of sci_cb_close_block */ -static void get_indent(ScintillaObject *sci, gint pos, gboolean use_this_line) -{ - guint i, len, j = 0; - gint prev_line; - gchar *linebuf; - - prev_line = sci_get_line_from_position(sci, pos); - - if (! use_this_line) prev_line--; - len = sci_get_line_length(sci, prev_line); - linebuf = sci_get_line(sci, prev_line); - - for (i = 0; i < len && j <= (sizeof(indent) - 1); i++) - { - if (linebuf[i] == ' ' || linebuf[i] == '\t') // simple indentation - indent[j++] = linebuf[i]; - else if (app->pref_editor_indention_mode != INDENT_ADVANCED) - break; - else if (use_this_line) - break; - else // sci_cb_close_block - { - if (! lexer_has_braces(sci)) - break; - - if (linebuf[i] == '{') - { - do_indent(indent, sizeof(indent), &j); - break; - } - else - { - gint k = len - 1; - - while (k > 0 && isspace(linebuf[k])) k--; - - // if last non-whitespace character is a { increase indentation by a tab - // e.g. for (...) { - if (linebuf[k] == '{') - { - do_indent(indent, sizeof(indent), &j); - } - break; - } - } - } - indent[j] = '\0'; - g_free(linebuf); -} - - -static void auto_close_bracket(ScintillaObject *sci, gint pos, gchar c) -{ - if (! app->pref_editor_auto_complete_constructs || SSM(sci, SCI_GETLEXER, 0, 0) != SCLEX_LATEX) - return; - - if (c == '[') - { - sci_add_text(sci, "]"); - } - else if (c == '{') - { - sci_add_text(sci, "}"); - } - sci_set_current_position(sci, pos, TRUE); -} - - -/* Finds a corresponding matching brace to the given pos - * (this is taken from Scintilla Editor.cxx, - * fit to work with sci_cb_close_block) */ -static gint brace_match(ScintillaObject *sci, gint pos) -{ - gchar chBrace = sci_get_char_at(sci, pos); - gchar chSeek = utils_brace_opposite(chBrace); - gchar chAtPos; - gint direction = -1; - gint styBrace; - gint depth = 1; - gint styAtPos; - - styBrace = sci_get_style_at(sci, pos); - - if (utils_is_opening_brace(chBrace)) - direction = 1; - - pos = pos + direction; - while ((pos >= 0) && (pos < sci_get_length(sci))) - { - chAtPos = sci_get_char_at(sci, pos - 1); - styAtPos = sci_get_style_at(sci, pos); - - if ((pos > sci_get_end_styled(sci)) || (styAtPos == styBrace)) - { - if (chAtPos == chBrace) - depth++; - if (chAtPos == chSeek) - depth--; - if (depth == 0) - return pos; - } - pos = pos + direction; - } - return -1; -} - - -/* Called after typing '}', if pref_editor_indention_mode is INDENT_ADVANCED. */ -void sci_cb_close_block(gint idx, gint pos) -{ - gint x = 0, cnt = 0; - gint start_brace, line, line_len, eol_char_len; - gchar *text, *line_buf; - ScintillaObject *sci; - - if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return; - - sci = doc_list[idx].sci; - - if (! lexer_has_braces(sci)) - return; - - line = sci_get_line_from_position(sci, pos); - line_len = sci_get_line_length(sci, line); - // set eol_char_len to 0 if on last line, because there is no EOL char - eol_char_len = (line == (SSM(sci, SCI_GETLINECOUNT, 0, 0) - 1)) ? 0 : - utils_get_eol_char_len(document_find_by_sci(sci)); - - // check that the line is empty, to not kill text in the line - line_buf = sci_get_line(sci, line); - line_buf[line_len - eol_char_len] = '\0'; - while (x < (line_len - eol_char_len)) - { - if (isspace(line_buf[x])) cnt++; - x++; - } - g_free(line_buf); - - if ((line_len - eol_char_len - 1) != cnt) return; - - start_brace = brace_match(sci, pos); // same as sci_find_bracematch (Document::BraceMatch)? - - if (start_brace >= 0) - { - gint line_start; - - get_indent(sci, start_brace, TRUE); - text = g_strconcat(indent, "}", NULL); - line_start = sci_get_position_from_line(sci, line); - sci_set_anchor(sci, line_start); - SSM(sci, SCI_REPLACESEL, 0, (sptr_t) text); - g_free(text); - } - else - if (sci_get_lexer(sci) == SCLEX_HTML || sci_get_lexer(sci) == SCLEX_TCL) - { /* For TCL & PHP brace_match doesn't work here (maybe lexer bugs?), - * so this is a simple workaround. */ - gint line_indent = sci_get_line_indentation(sci, line); - gint last_indent = sci_get_line_indentation(sci, line - 1); - - if (line_indent < last_indent) - return; - line_indent -= app->pref_editor_tab_width; - line_indent = MAX(0, line_indent); - sci_set_line_indentation(sci, line, line_indent); - } -} - - -/* Reads the word at given cursor position and writes it into the given buffer. The buffer will be - * NULL terminated in any case, even when the word is truncated because wordlen is too small. - * position can be -1, then the current position is used. - * wc are the wordchars to use, if NULL, GEANY_WORDCHARS will be used */ -void sci_cb_find_current_word(ScintillaObject *sci, gint pos, gchar *word, size_t wordlen, - const gchar *wc) -{ - gint line, line_start, startword, endword; - gchar *chunk; - - if (pos == -1) - pos = sci_get_current_position(sci); - - line = sci_get_line_from_position(sci, pos); - line_start = sci_get_position_from_line(sci, line); - startword = pos - line_start; - endword = pos - line_start; - - word[0] = '\0'; - chunk = sci_get_line(sci, line); - - if (wc == NULL) - wc = GEANY_WORDCHARS; - - while (startword > 0 && strchr(wc, chunk[startword - 1])) - startword--; - while (chunk[endword] && strchr(wc, chunk[endword])) - endword++; - if(startword == endword) - return; - - chunk[endword] = '\0'; - - g_strlcpy(word, chunk + startword, wordlen); //ensure null terminated - g_free(chunk); -} - - -static gint find_previous_brace(ScintillaObject *sci, gint pos) -{ - gchar c; - gint orig_pos = pos; - - c = SSM(sci, SCI_GETCHARAT, pos, 0); - while (pos >= 0 && pos > orig_pos - 300) - { - c = SSM(sci, SCI_GETCHARAT, pos, 0); - pos--; - if (utils_is_opening_brace(c)) return pos; - } - return -1; -} - - -static gint find_start_bracket(ScintillaObject *sci, gint pos) -{ - gchar c; - gint brackets = 0; - gint orig_pos = pos; - - c = SSM(sci, SCI_GETCHARAT, pos, 0); - while (pos > 0 && pos > orig_pos - 300) - { - c = SSM(sci, SCI_GETCHARAT, pos, 0); - if (c == ')') brackets++; - else if (c == '(') brackets--; - pos--; - if (brackets < 0) return pos; // found start bracket - } - return -1; -} - - -static gboolean append_calltip(GString *str, const TMTag *tag, filetype_id ft_id) -{ - if (! tag->atts.entry.arglist) return FALSE; - - if (tag->atts.entry.var_type) - { - guint i; - - g_string_append(str, tag->atts.entry.var_type); - for (i = 0; i < tag->atts.entry.pointerOrder; i++) - { - g_string_append_c(str, '*'); - } - g_string_append_c(str, ' '); - } - if (tag->atts.entry.scope) - { - const gchar *cosep = symbols_get_context_separator(ft_id); - - g_string_append(str, tag->atts.entry.scope); - g_string_append(str, cosep); - } - g_string_append(str, tag->name); - g_string_append_c(str, ' '); - g_string_append(str, tag->atts.entry.arglist); - - return TRUE; -} - - -static gchar *find_calltip(const gchar *word, filetype *ft) -{ - const GPtrArray *tags; - const gint arg_types = tm_tag_function_t | tm_tag_prototype_t | - tm_tag_method_t | tm_tag_macro_with_arg_t; - TMTagAttrType *attrs = NULL; - TMTag *tag; - GString *str = NULL; - guint i; - - g_return_val_if_fail(ft && word && *word, NULL); - - tags = tm_workspace_find(word, arg_types | tm_tag_class_t, attrs, FALSE, ft->lang); - if (tags->len == 0) - return NULL; - - tag = TM_TAG(tags->pdata[0]); - - if (tag->type == tm_tag_class_t && FILETYPE_ID(ft) == GEANY_FILETYPES_D) - { - // user typed e.g. 'new Classname(' so lookup D constructor Classname::this() - tags = tm_workspace_find_scoped("this", tag->name, - arg_types, attrs, FALSE, ft->lang, TRUE); - if (tags->len == 0) - return NULL; - } - - // remove tags with no argument list - for (i = 0; i < tags->len; i++) - { - tag = TM_TAG(tags->pdata[i]); - - if (! tag->atts.entry.arglist) - tags->pdata[i] = NULL; - } - tm_tags_prune((GPtrArray *) tags); - if (tags->len == 0) - return NULL; - else - { // remove duplicate calltips - TMTagAttrType sort_attr[] = {tm_tag_attr_name_t, tm_tag_attr_scope_t, - tm_tag_attr_arglist_t, 0}; - - tm_tags_sort((GPtrArray *) tags, sort_attr, TRUE); - } - - // if the current word has changed since last time, start with the first tag match - if (! utils_str_equal(word, calltip.last_word)) - calltip.tag_index = 0; - // cache the current word for next time - g_free(calltip.last_word); - calltip.last_word = g_strdup(word); - calltip.tag_index = MIN(calltip.tag_index, tags->len - 1); // ensure tag_index is in range - - for (i = calltip.tag_index; i < tags->len; i++) - { - tag = TM_TAG(tags->pdata[i]); - - if (str == NULL) - { - str = g_string_new(NULL); - if (calltip.tag_index > 0) - g_string_prepend(str, "\001 "); // up arrow - append_calltip(str, tag, FILETYPE_ID(ft)); - } - else // add a down arrow - { - if (calltip.tag_index > 0) // already have an up arrow - g_string_insert_c(str, 1, '\002'); - else - g_string_prepend(str, "\002 "); - break; - } - } - if (str) - { - gchar *result = str->str; - - g_string_free(str, FALSE); - return result; - } - return NULL; -} - - -// use pos = -1 to search for the previous unmatched open bracket. -gboolean sci_cb_show_calltip(gint idx, gint pos) -{ - gint orig_pos = pos; // the position for the calltip - gint lexer; - gint style; - gchar word[GEANY_MAX_WORD_LENGTH]; - gchar *str; - ScintillaObject *sci; - - if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return FALSE; - sci = doc_list[idx].sci; - - lexer = SSM(sci, SCI_GETLEXER, 0, 0); - - if (pos == -1) - { - // position of '(' is unknown, so go backwards from current position to find it - pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); - pos--; - orig_pos = pos; - pos = (lexer == SCLEX_LATEX) ? find_previous_brace(sci, pos) : - find_start_bracket(sci, pos); - if (pos == -1) return FALSE; - } - - style = SSM(sci, SCI_GETSTYLEAT, pos, 0); - if (is_comment(lexer, style)) - return FALSE; - // never show a calltip in a PHP file outside of the <? ?> tags - if (lexer == SCLEX_HTML && ! (style >= SCE_HPHP_DEFAULT && style <= SCE_HPHP_OPERATOR)) - return FALSE; - - word[0] = '\0'; - sci_cb_find_current_word(sci, pos - 1, word, sizeof word, NULL); - if (word[0] == '\0') return FALSE; - - str = find_calltip(word, doc_list[idx].file_type); - if (str) - { - g_free(calltip.text); // free the old calltip - calltip.text = str; - calltip.set = TRUE; - utils_wrap_string(calltip.text, -1); - SSM(sci, SCI_CALLTIPSHOW, orig_pos, (sptr_t) calltip.text); - return TRUE; - } - return FALSE; -} - - -static void show_autocomplete(ScintillaObject *sci, gint rootlen, const gchar *words) -{ - // store whether a calltip is showing, so we can reshow it after autocompletion - calltip.set = SSM(sci, SCI_CALLTIPACTIVE, 0, 0); - SSM(sci, SCI_AUTOCSHOW, rootlen, (sptr_t) words); -} - - -static gboolean -autocomplete_html(ScintillaObject *sci, const gchar *root, gsize rootlen) -{ // HTML entities auto completion - guint i, j = 0; - GString *words; - const gchar **entities = symbols_get_html_entities(); - - if (*root != '&' || entities == NULL) return FALSE; - - words = g_string_sized_new(500); - for (i = 0; ; i++) - { - if (entities[i] == NULL) break; - else if (entities[i][0] == '#') continue; - - if (! strncmp(entities[i], root, rootlen)) - { - if (j++ > 0) g_string_append_c(words, ' '); - g_string_append(words, entities[i]); - } - } - if (words->len > 0) show_autocomplete(sci, rootlen, words->str); - g_string_free(words, TRUE); - return TRUE; -} - - -static gboolean -autocomplete_tags(gint idx, gchar *root, gsize rootlen) -{ // PHP, LaTeX, C, C++, D and Java tag autocompletion - TMTagAttrType attrs[] = { tm_tag_attr_name_t, 0 }; - const GPtrArray *tags; - ScintillaObject *sci; - - if (! DOC_IDX_VALID(idx) || doc_list[idx].file_type == NULL) - return FALSE; - sci = doc_list[idx].sci; - - tags = tm_workspace_find(root, tm_tag_max_t, attrs, TRUE, doc_list[idx].file_type->lang); - if (NULL != tags && tags->len > 0) - { - GString *words = g_string_sized_new(150); - guint j; - - for (j = 0; ((j < tags->len) && (j < GEANY_MAX_AUTOCOMPLETE_WORDS)); ++j) - { - if (j > 0) g_string_append_c(words, ' '); - g_string_append(words, ((TMTag *) tags->pdata[j])->name); - } - show_autocomplete(sci, rootlen, words->str); - g_string_free(words, TRUE); - } - return TRUE; -} - - -gboolean sci_cb_start_auto_complete(gint idx, gint pos, gboolean force) -{ - gint line, line_start, line_len, line_pos, current, rootlen, startword, lexer, style; - gchar *linebuf, *root; - ScintillaObject *sci; - gboolean ret = FALSE; - gchar *wordchars; - filetype *ft; - - if ((! app->pref_editor_auto_complete_symbols && ! force) || - ! DOC_IDX_VALID(idx) || doc_list[idx].file_type == NULL) - return FALSE; - - sci = doc_list[idx].sci; - ft = doc_list[idx].file_type; - - line = sci_get_line_from_position(sci, pos); - line_start = sci_get_position_from_line(sci, line); - line_len = sci_get_line_length(sci, line); - line_pos = pos - line_start - 1; - current = pos - line_start; - startword = current; - lexer = SSM(sci, SCI_GETLEXER, 0, 0); - style = SSM(sci, SCI_GETSTYLEAT, pos, 0); - - // don't autocomplete in comments and strings - if (is_comment(lexer, style)) - return FALSE; - - linebuf = sci_get_line(sci, line); - - if (ft->id == GEANY_FILETYPES_LATEX) - wordchars = GEANY_WORDCHARS"\"; // add \ to word chars if we are in a LaTeX file - else if (ft->id == GEANY_FILETYPES_HTML || ft->id == GEANY_FILETYPES_PHP) - wordchars = GEANY_WORDCHARS"&"; // add & to word chars if we are in a PHP or HTML file - else - wordchars = GEANY_WORDCHARS; - - // find the start of the current word - while ((startword > 0) && (strchr(wordchars, linebuf[startword - 1]))) - startword--; - linebuf[current] = '\0'; - root = linebuf + startword; - rootlen = current - startword; - - // entity autocompletion always in a HTML file, in a PHP file only when we are outside of <? ?> - if (ft->id == GEANY_FILETYPES_HTML || - (ft->id == GEANY_FILETYPES_PHP && (style < SCE_HPHP_DEFAULT || style > SCE_HPHP_OPERATOR))) - ret = autocomplete_html(sci, root, rootlen); - else - { - // force is set when called by keyboard shortcut, otherwise start at the 4th char - if (force || rootlen >= 4) - ret = autocomplete_tags(idx, root, rootlen); - } - - g_free(linebuf); - return ret; -} - - -void sci_cb_auto_latex(gint idx, gint pos) -{ - ScintillaObject *sci; - - if (idx == -1 || ! doc_list[idx].is_valid || doc_list[idx].file_type == NULL) return; - sci = doc_list[idx].sci; - - if (sci_get_char_at(sci, pos - 2) == '}') - { - gchar *eol, *buf, *construct; - gchar env[50]; - gint line = sci_get_line_from_position(sci, pos - 2); - gint line_len = sci_get_line_length(sci, line); - gint i, start; - - // get the line - buf = sci_get_line(sci, line); - - // get to the first non-blank char (some kind of ltrim()) - start = 0; - //while (isspace(buf[i++])) start++; - while (isspace(buf[start])) start++; - - // check for begin - if (strncmp(buf + start, "\begin", 6) == 0) - { - gchar full_cmd[15]; - guint j = 0; - - // take also "\begingroup" (or whatever there can be) and append "\endgroup" and so on. - i = start + 6; - while (i < line_len && buf[i] != '{' && j < (sizeof(full_cmd) - 1)) - { // copy all between "\begin" and "{" to full_cmd - full_cmd[j] = buf[i]; - i++; - j++; - } - full_cmd[j] = '\0'; - - // go through the line and get the environment - for (i = start + j; i < line_len; i++) - { - if (buf[i] == '{') - { - j = 0; - i++;
@@ Diff output truncated at 100000 characters. @@
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.