You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

420 lines
10KB

  1. //
  2. // "$Id: CodeEditor.cxx 7903 2010-11-28 21:06:39Z matt $"
  3. //
  4. // Code editor widget for the Fast Light Tool Kit (FLTK).
  5. //
  6. // Copyright 1998-2010 by Bill Spitzak and others.
  7. //
  8. // This library is free software; you can redistribute it and/or
  9. // modify it under the terms of the GNU Library General Public
  10. // License as published by the Free Software Foundation; either
  11. // version 2 of the License, or (at your option) any later version.
  12. //
  13. // This library is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. // Library General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Library General Public
  19. // License along with this library; if not, write to the Free Software
  20. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  21. // USA.
  22. //
  23. // Please report all bugs and problems on the following page:
  24. //
  25. // http://www.fltk.org/str.php
  26. //
  27. //
  28. // Include necessary headers...
  29. //
  30. #include <stdio.h>
  31. #include <stdlib.h>
  32. #include <string.h>
  33. #include <ctype.h>
  34. #include "CodeEditor.h"
  35. Fl_Text_Display::Style_Table_Entry CodeEditor::
  36. styletable[] = { // Style table
  37. { FL_FOREGROUND_COLOR, FL_COURIER, 11 }, // A - Plain
  38. { FL_DARK_GREEN, FL_COURIER_ITALIC, 11 }, // B - Line comments
  39. { FL_DARK_GREEN, FL_COURIER_ITALIC, 11 }, // C - Block comments
  40. { FL_BLUE, FL_COURIER, 11 }, // D - Strings
  41. { FL_DARK_RED, FL_COURIER, 11 }, // E - Directives
  42. { FL_DARK_RED, FL_COURIER_BOLD, 11 }, // F - Types
  43. { FL_BLUE, FL_COURIER_BOLD, 11 } // G - Keywords
  44. };
  45. const char * const CodeEditor::
  46. code_keywords[] = { // Sorted list of C/C++ keywords...
  47. "and",
  48. "and_eq",
  49. "asm",
  50. "bitand",
  51. "bitor",
  52. "break",
  53. "case",
  54. "catch",
  55. "compl",
  56. "continue",
  57. "default",
  58. "delete",
  59. "do",
  60. "else",
  61. "false",
  62. "for",
  63. "goto",
  64. "if",
  65. "new",
  66. "not",
  67. "not_eq",
  68. "operator",
  69. "or",
  70. "or_eq",
  71. "return",
  72. "switch",
  73. "template",
  74. "this",
  75. "throw",
  76. "true",
  77. "try",
  78. "while",
  79. "xor",
  80. "xor_eq"
  81. };
  82. const char * const CodeEditor::
  83. code_types[] = { // Sorted list of C/C++ types...
  84. "auto",
  85. "bool",
  86. "char",
  87. "class",
  88. "const",
  89. "const_cast",
  90. "double",
  91. "dynamic_cast",
  92. "enum",
  93. "explicit",
  94. "extern",
  95. "float",
  96. "friend",
  97. "inline",
  98. "int",
  99. "long",
  100. "mutable",
  101. "namespace",
  102. "private",
  103. "protected",
  104. "public",
  105. "register",
  106. "short",
  107. "signed",
  108. "sizeof",
  109. "static",
  110. "static_cast",
  111. "struct",
  112. "template",
  113. "typedef",
  114. "typename",
  115. "union",
  116. "unsigned",
  117. "virtual",
  118. "void",
  119. "volatile"
  120. };
  121. // 'compare_keywords()' - Compare two keywords...
  122. int CodeEditor::compare_keywords(const void *a, const void *b) {
  123. return (strcmp(*((const char **)a), *((const char **)b)));
  124. }
  125. // 'style_parse()' - Parse text and produce style data.
  126. void CodeEditor::style_parse(const char *text, char *style, int length) {
  127. char current;
  128. int col;
  129. int last;
  130. char buf[255],
  131. *bufptr;
  132. const char *temp;
  133. // Style letters:
  134. //
  135. // A - Plain
  136. // B - Line comments
  137. // C - Block comments
  138. // D - Strings
  139. // E - Directives
  140. // F - Types
  141. // G - Keywords
  142. for (current = *style, col = 0, last = 0; length > 0; length --, text ++) {
  143. if (current == 'B' || current == 'F' || current == 'G') current = 'A';
  144. if (current == 'A') {
  145. // Check for directives, comments, strings, and keywords...
  146. if (col == 0 && *text == '#') {
  147. // Set style to directive
  148. current = 'E';
  149. } else if (strncmp(text, "//", 2) == 0) {
  150. current = 'B';
  151. for (; length > 0 && *text != '\n'; length --, text ++) *style++ = 'B';
  152. if (length == 0) break;
  153. } else if (strncmp(text, "/*", 2) == 0) {
  154. current = 'C';
  155. } else if (strncmp(text, "\\\"", 2) == 0) {
  156. // Quoted quote...
  157. *style++ = current;
  158. *style++ = current;
  159. text ++;
  160. length --;
  161. col += 2;
  162. continue;
  163. } else if (*text == '\"') {
  164. current = 'D';
  165. } else if (!last && (islower(*text) || *text == '_')) {
  166. // Might be a keyword...
  167. for (temp = text, bufptr = buf;
  168. (islower(*temp) || *temp == '_') && bufptr < (buf + sizeof(buf) - 1);
  169. *bufptr++ = *temp++);
  170. if (!islower(*temp) && *temp != '_') {
  171. *bufptr = '\0';
  172. bufptr = buf;
  173. if (bsearch(&bufptr, code_types,
  174. sizeof(code_types) / sizeof(code_types[0]),
  175. sizeof(code_types[0]), compare_keywords)) {
  176. while (text < temp) {
  177. *style++ = 'F';
  178. text ++;
  179. length --;
  180. col ++;
  181. }
  182. text --;
  183. length ++;
  184. last = 1;
  185. continue;
  186. } else if (bsearch(&bufptr, code_keywords,
  187. sizeof(code_keywords) / sizeof(code_keywords[0]),
  188. sizeof(code_keywords[0]), compare_keywords)) {
  189. while (text < temp) {
  190. *style++ = 'G';
  191. text ++;
  192. length --;
  193. col ++;
  194. }
  195. text --;
  196. length ++;
  197. last = 1;
  198. continue;
  199. }
  200. }
  201. }
  202. } else if (current == 'C' && strncmp(text, "*/", 2) == 0) {
  203. // Close a C comment...
  204. *style++ = current;
  205. *style++ = current;
  206. text ++;
  207. length --;
  208. current = 'A';
  209. col += 2;
  210. continue;
  211. } else if (current == 'D') {
  212. // Continuing in string...
  213. if (strncmp(text, "\\\"", 2) == 0) {
  214. // Quoted end quote...
  215. *style++ = current;
  216. *style++ = current;
  217. text ++;
  218. length --;
  219. col += 2;
  220. continue;
  221. } else if (*text == '\"') {
  222. // End quote...
  223. *style++ = current;
  224. col ++;
  225. current = 'A';
  226. continue;
  227. }
  228. }
  229. // Copy style info...
  230. if (current == 'A' && (*text == '{' || *text == '}')) *style++ = 'G';
  231. else *style++ = current;
  232. col ++;
  233. last = isalnum(*text) || *text == '_' || *text == '.';
  234. if (*text == '\n') {
  235. // Reset column and possibly reset the style
  236. col = 0;
  237. if (current == 'B' || current == 'E') current = 'A';
  238. }
  239. }
  240. }
  241. // 'style_unfinished_cb()' - Update unfinished styles.
  242. void CodeEditor::style_unfinished_cb(int, void*) { }
  243. // 'style_update()' - Update the style buffer...
  244. void CodeEditor::style_update(int pos, int nInserted, int nDeleted,
  245. int /*nRestyled*/, const char * /*deletedText*/,
  246. void *cbArg) {
  247. CodeEditor *editor = (CodeEditor *)cbArg;
  248. int start, // Start of text
  249. end; // End of text
  250. char last, // Last style on line
  251. *style, // Style data
  252. *text; // Text data
  253. // If this is just a selection change, just unselect the style buffer...
  254. if (nInserted == 0 && nDeleted == 0) {
  255. editor->mStyleBuffer->unselect();
  256. return;
  257. }
  258. // Track changes in the text buffer...
  259. if (nInserted > 0) {
  260. // Insert characters into the style buffer...
  261. style = new char[nInserted + 1];
  262. memset(style, 'A', nInserted);
  263. style[nInserted] = '\0';
  264. editor->mStyleBuffer->replace(pos, pos + nDeleted, style);
  265. delete[] style;
  266. } else {
  267. // Just delete characters in the style buffer...
  268. editor->mStyleBuffer->remove(pos, pos + nDeleted);
  269. }
  270. // Select the area that was just updated to avoid unnecessary
  271. // callbacks...
  272. editor->mStyleBuffer->select(pos, pos + nInserted - nDeleted);
  273. // Re-parse the changed region; we do this by parsing from the
  274. // beginning of the line of the changed region to the end of
  275. // the line of the changed region... Then we check the last
  276. // style character and keep updating if we have a multi-line
  277. // comment character...
  278. start = editor->mBuffer->line_start(pos);
  279. end = editor->mBuffer->line_end(pos + nInserted);
  280. text = editor->mBuffer->text_range(start, end);
  281. style = editor->mStyleBuffer->text_range(start, end);
  282. if (start==end)
  283. last = 0;
  284. else
  285. last = style[end - start - 1];
  286. style_parse(text, style, end - start);
  287. editor->mStyleBuffer->replace(start, end, style);
  288. editor->redisplay_range(start, end);
  289. if (start==end || last != style[end - start - 1]) {
  290. // The last character on the line changed styles, so reparse the
  291. // remainder of the buffer...
  292. free(text);
  293. free(style);
  294. end = editor->mBuffer->length();
  295. text = editor->mBuffer->text_range(start, end);
  296. style = editor->mStyleBuffer->text_range(start, end);
  297. style_parse(text, style, end - start);
  298. editor->mStyleBuffer->replace(start, end, style);
  299. editor->redisplay_range(start, end);
  300. }
  301. free(text);
  302. free(style);
  303. }
  304. int CodeEditor::auto_indent(int, CodeEditor* e) {
  305. if (e->buffer()->selected()) {
  306. e->insert_position(e->buffer()->primary_selection()->start());
  307. e->buffer()->remove_selection();
  308. }
  309. int pos = e->insert_position();
  310. int start = e->line_start(pos);
  311. char *text = e->buffer()->text_range(start, pos);
  312. char *ptr;
  313. for (ptr = text; isspace(*ptr); ptr ++);
  314. *ptr = '\0';
  315. if (*text) {
  316. // use only a single 'insert' call to avoid redraw issues
  317. int n = strlen(text);
  318. char *b = (char*)malloc(n+2);
  319. *b = '\n';
  320. strcpy(b+1, text);
  321. e->insert(b);
  322. free(b);
  323. } else {
  324. e->insert("\n");
  325. }
  326. e->show_insert_position();
  327. e->set_changed();
  328. if (e->when()&FL_WHEN_CHANGED) e->do_callback();
  329. free(text);
  330. return 1;
  331. }
  332. // Create a CodeEditor widget...
  333. CodeEditor::CodeEditor(int X, int Y, int W, int H, const char *L) :
  334. Fl_Text_Editor(X, Y, W, H, L) {
  335. buffer(new Fl_Text_Buffer);
  336. char *style = new char[mBuffer->length() + 1];
  337. char *text = mBuffer->text();
  338. memset(style, 'A', mBuffer->length());
  339. style[mBuffer->length()] = '\0';
  340. highlight_data(new Fl_Text_Buffer(mBuffer->length()), styletable,
  341. sizeof(styletable) / sizeof(styletable[0]),
  342. 'A', style_unfinished_cb, this);
  343. style_parse(text, style, mBuffer->length());
  344. mStyleBuffer->text(style);
  345. delete[] style;
  346. free(text);
  347. mBuffer->add_modify_callback(style_update, this);
  348. add_key_binding(FL_Enter, FL_TEXT_EDITOR_ANY_STATE,
  349. (Fl_Text_Editor::Key_Func)auto_indent);
  350. }
  351. // Destroy a CodeEditor widget...
  352. CodeEditor::~CodeEditor() {
  353. Fl_Text_Buffer *buf = mStyleBuffer;
  354. mStyleBuffer = 0;
  355. delete buf;
  356. buf = mBuffer;
  357. buffer(0);
  358. delete buf;
  359. }
  360. CodeViewer::CodeViewer(int X, int Y, int W, int H, const char *L)
  361. : CodeEditor(X, Y, W, H, L)
  362. {
  363. default_key_function(kf_ignore);
  364. remove_all_key_bindings(&key_bindings);
  365. cursor_style(CARET_CURSOR);
  366. }
  367. //
  368. // End of "$Id: CodeEditor.cxx 7903 2010-11-28 21:06:39Z matt $".
  369. //