Text editor: syntax highlighting + line numbers on by default
authorSybren A. Stüvel <sybren@blender.org>
Tue, 13 Aug 2019 13:35:48 +0000 (15:35 +0200)
committerSybren A. Stüvel <sybren@blender.org>
Wed, 14 Aug 2019 14:59:37 +0000 (16:59 +0200)
The most common use of the text editor seems to be for scripting. Having
line numbers and syntax highlighting enabled by default seems sensible.

Syntax highlighting is now enabled by default, but is automatically
disabled when the datablock has a non-highlighted extension.
Highlighting is enabled for filenames like:
    - Text
    - Text.001
    - somefile.py
and is automatically disabled when the datablock has an extension for
which Blender has no syntax highlighter registered.

Reviewers: billreynish, campbellbarton

Subscribers: brecht, billreynish

Differential Revision: https://developer.blender.org/D5472

14 files changed:
release/scripts/startup/bl_ui/space_text.py
source/blender/blenlib/BLI_path_util.h
source/blender/blenlib/BLI_string_utils.h
source/blender/blenlib/intern/path_util.c
source/blender/blenlib/intern/string_utils.c
source/blender/editors/include/ED_text.h
source/blender/editors/space_text/space_text.c
source/blender/editors/space_text/text_draw.c
source/blender/editors/space_text/text_format.c
source/blender/makesrna/intern/rna_space.c
source/blender/makesrna/intern/rna_text.c
source/blender/makesrna/intern/rna_text_api.c
tests/gtests/blenlib/BLI_path_util_test.cc
tests/gtests/blenlib/BLI_string_test.cc

index 1ffb181b2196950e33b571cdb9e4ebfc5e5e6800..e82c6bc5dc723fdddc28f4933916b7f27bce105a 100644 (file)
@@ -50,7 +50,11 @@ class TEXT_HT_header(Header):
         row = layout.row(align=True)
         row.prop(st, "show_line_numbers", text="")
         row.prop(st, "show_word_wrap", text="")
-        row.prop(st, "show_syntax_highlight", text="")
+
+        is_syntax_highlight_supported = st.is_syntax_highlight_supported()
+        syntax = row.row(align=True)
+        syntax.active = is_syntax_highlight_supported
+        syntax.prop(st, "show_syntax_highlight", text="")
 
         if text:
             is_osl = text.name.endswith((".osl", ".osl"))
@@ -65,6 +69,7 @@ class TEXT_HT_header(Header):
                 row.prop(text, "use_module")
 
                 row = layout.row()
+                row.active = is_syntax_highlight_supported
                 row.operator("text.run_script")
 
 
@@ -226,7 +231,9 @@ class TEXT_MT_view(Menu):
 
         layout.prop(st, "show_line_numbers")
         layout.prop(st, "show_word_wrap")
-        layout.prop(st, "show_syntax_highlight")
+        syntax = layout.column()
+        syntax.active = st.is_syntax_highlight_supported()
+        syntax.prop(st, "show_syntax_highlight")
         layout.prop(st, "show_line_highlight")
 
         layout.separator()
index 31b68204c51f4177f6fbfad07a01aa0802555eae..99e86615e50ac2c0057d21c5bcd94f5d749b26d5 100644 (file)
@@ -42,6 +42,8 @@ void BLI_split_dirfile(
     const char *string, char *dir, char *file, const size_t dirlen, const size_t filelen);
 void BLI_split_dir_part(const char *string, char *dir, const size_t dirlen);
 void BLI_split_file_part(const char *string, char *file, const size_t filelen);
+const char *BLI_path_extension(const char *filepath) ATTR_NONNULL();
+
 void BLI_path_append(char *__restrict dst, const size_t maxlen, const char *__restrict file)
     ATTR_NONNULL();
 void BLI_join_dirfile(char *__restrict string,
index 9740629276d8cc99e9fe6edc36a327376da71fc4..13dbb2de659c8b495a56cbdd10b99440d12e87d9 100644 (file)
@@ -38,6 +38,7 @@ struct ListBase;
 typedef bool (*UniquenameCheckCallback)(void *arg, const char *name);
 
 size_t BLI_split_name_num(char *left, int *nr, const char *name, const char delim);
+bool BLI_string_is_decimal(const char *string) ATTR_NONNULL();
 
 void BLI_string_split_suffix(const char *string, char *r_body, char *r_suf, const size_t str_len);
 void BLI_string_split_prefix(const char *string, char *r_pre, char *r_body, const size_t str_len);
index 111b530a527a3460574aa2cf55c7db3361aee356..18acbca00a138cdb7c393ec0f5e7636e046f871e 100644 (file)
@@ -1684,6 +1684,24 @@ void BLI_split_file_part(const char *string, char *file, const size_t filelen)
   BLI_split_dirfile(string, NULL, file, 0, filelen);
 }
 
+/**
+ * Returns a pointer to the last extension (e.g. the position of the last period).
+ * Returns NULL if there is no extension.
+ */
+const char *BLI_path_extension(const char *filepath)
+{
+  const char *extension = strrchr(filepath, '.');
+  if (extension == NULL) {
+    return NULL;
+  }
+  if (BLI_first_slash(extension) != NULL) {
+    /* There is a path separator in the extension, so the '.' was found in a
+     * directory component and not in the filename. */
+    return NULL;
+  }
+  return extension;
+}
+
 /**
  * Append a filename to a dir, ensuring slash separates.
  */
index f2b3ef2ad877358ce25165776f92a42c36164177..b956e1c0a7ed0e9e8f555084af29a0adca9fe8a0 100644 (file)
@@ -82,6 +82,21 @@ size_t BLI_split_name_num(char *left, int *nr, const char *name, const char deli
   return name_len;
 }
 
+bool BLI_string_is_decimal(const char *string)
+{
+  if (*string == '\0') {
+    return false;
+  }
+
+  /* Keep iterating over the string until a non-digit is found. */
+  while (isdigit(*string)) {
+    string++;
+  }
+
+  /* If the non-digit we found is the terminating \0, everything was digits. */
+  return *string == '\0';
+}
+
 static bool is_char_sep(const char c)
 {
   return ELEM(c, '.', ' ', '-', '_');
index 39d0b8b26cb4afe1287c141b316a1ab9f3e1a1c7..40af82acdaf1da3f95c3604ce359a067b1a72bf8 100644 (file)
@@ -27,6 +27,7 @@
 struct ARegion;
 struct bContext;
 struct SpaceText;
+struct Text;
 struct UndoStep;
 struct UndoType;
 
@@ -40,4 +41,7 @@ void ED_text_undosys_type(struct UndoType *ut);
 
 struct UndoStep *ED_text_undo_push_init(struct bContext *C);
 
+/* text_format.c */
+bool ED_text_is_syntax_highlight_supported(struct Text *text);
+
 #endif /* __ED_TEXT_H__ */
index 8bfb6c87625b43a8fdf77d05fafe523a43511d92..c1a3c79b0d8036e6575ff136dc3ae2e4438af90e 100644 (file)
@@ -63,6 +63,8 @@ static SpaceLink *text_new(const ScrArea *UNUSED(area), const Scene *UNUSED(scen
   stext->lheight = 12;
   stext->tabnumber = 4;
   stext->margin_column = 80;
+  stext->showsyntax = true;
+  stext->showlinenrs = true;
 
   /* header */
   ar = MEM_callocN(sizeof(ARegion), "header for text");
index 9dc8dfa93b6005972a0094f31a94699bc52f7da6..e99bf680077d479c96119503f48ed956f267e6ef 100644 (file)
@@ -54,6 +54,7 @@ typedef struct TextDrawContext {
   int font_id;
   int cwidth;
   int lheight_dpi;
+  bool syntax_highlight;
 } TextDrawContext;
 
 static void text_draw_context_init(const SpaceText *st, TextDrawContext *tdc)
@@ -61,6 +62,7 @@ static void text_draw_context_init(const SpaceText *st, TextDrawContext *tdc)
   tdc->font_id = blf_mono_font;
   tdc->cwidth = 0;
   tdc->lheight_dpi = st->lheight_dpi;
+  tdc->syntax_highlight = st->showsyntax && ED_text_is_syntax_highlight_supported(st->text);
 }
 
 static void text_font_begin(const TextDrawContext *tdc)
@@ -418,7 +420,7 @@ static int text_draw_wrapped(const SpaceText *st,
                              const char *format,
                              int skip)
 {
-  const bool use_syntax = (st->showsyntax && format);
+  const bool use_syntax = (tdc->syntax_highlight && format);
   FlattenString fs;
   int basex, lines;
   int i, wrap, end, max, columns, padding; /* column */
@@ -514,7 +516,7 @@ static void text_draw(const SpaceText *st,
                       int y,
                       const char *format)
 {
-  const bool use_syntax = (st->showsyntax && format);
+  const bool use_syntax = (tdc->syntax_highlight && format);
   FlattenString fs;
   int columns, size, n, w = 0, padding, amount = 0;
   const char *in = NULL;
@@ -1383,8 +1385,8 @@ static void draw_brackets(const SpaceText *st, const TextDrawContext *tdc, ARegi
 
   char ch;
 
-  // showsyntax must be on or else the format string will be null
-  if (!text->curl || !st->showsyntax) {
+  // syntax_highlight must be on or else the format string will be null
+  if (!text->curl || !tdc->syntax_highlight) {
     return;
   }
 
@@ -1576,7 +1578,7 @@ void draw_text_main(SpaceText *st, ARegion *ar)
   tmp = text->lines.first;
   lineno = 0;
   for (i = 0; i < st->top && tmp; i++) {
-    if (st->showsyntax && !tmp->format) {
+    if (tdc.syntax_highlight && !tmp->format) {
       tft->format_line(st, tmp, false);
     }
 
@@ -1631,7 +1633,7 @@ void draw_text_main(SpaceText *st, ARegion *ar)
   UI_FontThemeColor(tdc.font_id, TH_TEXT);
 
   for (i = 0; y > clip_min_y && i < st->viewlines && tmp; i++, tmp = tmp->next) {
-    if (st->showsyntax && !tmp->format) {
+    if (tdc.syntax_highlight && !tmp->format) {
       tft->format_line(st, tmp, false);
     }
 
index 8c102dc009e15a56ffdd2eb935b5ab204138b5aa..48ee30e450f07f93f444b3f7784ce690d0e37ad3 100644 (file)
 #include "MEM_guardedalloc.h"
 
 #include "BLI_blenlib.h"
+#include "BLI_string_utils.h"
 
 #include "DNA_text_types.h"
 #include "DNA_space_types.h"
 
+#include "ED_text.h"
+
 #include "text_format.h"
 
 /****************** flatten string **********************/
@@ -224,3 +227,38 @@ TextFormatType *ED_text_format_get(Text *text)
     return tft_lb.first;
   }
 }
+
+bool ED_text_is_syntax_highlight_supported(Text *text)
+{
+  if (text == NULL) {
+    return false;
+  }
+
+  TextFormatType *tft;
+
+  const char *text_ext = BLI_path_extension(text->id.name + 2);
+  if (text_ext == NULL) {
+    /* Extensionless datablocks are considered highlightable as Python. */
+    return true;
+  }
+  text_ext++; /* skip the '.' */
+  if (BLI_string_is_decimal(text_ext)) {
+    /* "Text.001" is treated as extensionless, and thus highlightable. */
+    return true;
+  }
+
+  /* Check all text formats in the static list */
+  for (tft = tft_lb.first; tft; tft = tft->next) {
+    /* All formats should have an ext, but just in case */
+    const char **ext;
+    for (ext = tft->ext; *ext; ext++) {
+      /* If extension matches text name, return the matching tft */
+      if (BLI_strcasecmp(text_ext, *ext) == 0) {
+        return true;
+      }
+    }
+  }
+
+  /* The filename has a non-numerical extension that we could not highlight. */
+  return false;
+}
index 6dc0cf045cdb33d50ace29115d3a84abf3074434..28f64e4e1f020597369689cc789949af99a041f4 100644 (file)
@@ -32,6 +32,8 @@
 #include "BKE_studiolight.h"
 #include "BKE_sequencer.h"
 
+#include "ED_text.h"
+
 #include "BLI_math.h"
 
 #include "DNA_action_types.h"
@@ -1504,6 +1506,11 @@ static void rna_SpaceTextEditor_text_set(PointerRNA *ptr,
   WM_main_add_notifier(NC_TEXT | NA_SELECTED, st->text);
 }
 
+static bool rna_SpaceTextEditor_text_is_syntax_highlight_supported(struct SpaceText *space)
+{
+  return ED_text_is_syntax_highlight_supported(space->text);
+}
+
 static void rna_SpaceTextEditor_updateEdited(Main *UNUSED(bmain),
                                              Scene *UNUSED(scene),
                                              PointerRNA *ptr)
@@ -4539,6 +4546,7 @@ static void rna_def_space_text(BlenderRNA *brna)
 {
   StructRNA *srna;
   PropertyRNA *prop;
+  FunctionRNA *func;
 
   srna = RNA_def_struct(brna, "SpaceTextEditor", "Space");
   RNA_def_struct_sdna(srna, "SpaceText");
@@ -4568,6 +4576,15 @@ static void rna_def_space_text(BlenderRNA *brna)
   RNA_def_property_ui_icon(prop, ICON_LINENUMBERS_ON, 0);
   RNA_def_property_update(prop, NC_SPACE | ND_SPACE_TEXT, NULL);
 
+  func = RNA_def_function(srna,
+                          "is_syntax_highlight_supported",
+                          "rna_SpaceTextEditor_text_is_syntax_highlight_supported");
+  RNA_def_function_return(func,
+                          RNA_def_boolean(func, "is_syntax_highlight_supported", false, "", ""));
+  RNA_def_function_ui_description(func,
+                                  "Returns True if the editor supports syntax highlighting "
+                                  "for the current text datablock");
+
   prop = RNA_def_property(srna, "show_syntax_highlight", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "showsyntax", 0);
   RNA_def_property_ui_text(prop, "Syntax Highlight", "Syntax highlight for scripting");
index b09b5327f57579835300bb72222201f7fecc0a0f..64a23dfa985f14daa976d9f4b37a28b91f8b76f4 100644 (file)
@@ -27,6 +27,8 @@
 
 #include "BKE_text.h"
 
+#include "ED_text.h"
+
 #include "RNA_define.h"
 
 #include "rna_internal.h"
index 4ca48226ee9e00e3b178d6389110d9e3c673b738..524dcfa9ad77f730827ff682a43d393c9e02f6f9 100644 (file)
@@ -23,6 +23,8 @@
 
 #include "BLI_utildefines.h"
 
+#include "ED_text.h"
+
 #include "RNA_define.h"
 
 #include "rna_internal.h" /* own include */
@@ -59,6 +61,14 @@ void RNA_api_text(StructRNA *srna)
       func, "write text at the cursor location and advance to the end of the text block");
   parm = RNA_def_string(func, "text", "Text", 0, "", "New text for this data-block");
   RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+
+  func = RNA_def_function(
+      srna, "is_syntax_highlight_supported", "ED_text_is_syntax_highlight_supported");
+  RNA_def_function_return(func,
+                          RNA_def_boolean(func, "is_syntax_highlight_supported", false, "", ""));
+  RNA_def_function_ui_description(func,
+                                  "Returns True if the editor supports syntax highlighting "
+                                  "for the current text datablock");
 }
 
 #endif
index c07ff2b0b05491e896264d6ccbadc613b1a159c2..c9a7436df31935ece3f38c4682a756c1c0924e56 100644 (file)
@@ -618,3 +618,18 @@ TEST(path_util, PathFrameGet)
   PATH_FRAME_GET("", -1, -1, false);
 }
 #undef PATH_FRAME_GET
+
+
+/* BLI_path_extension */
+TEST(path_util, PathExtension)
+{
+  EXPECT_EQ(NULL, BLI_path_extension("some.def/file"));
+  EXPECT_EQ(NULL, BLI_path_extension("Text"));
+  EXPECT_EQ(NULL, BLI_path_extension("Text…001"));
+
+  EXPECT_STREQ(".",  BLI_path_extension("some/file."));
+  EXPECT_STREQ(".gz",  BLI_path_extension("some/file.tar.gz"));
+  EXPECT_STREQ(".abc", BLI_path_extension("some.def/file.abc"));
+  EXPECT_STREQ(".abc", BLI_path_extension("C:\\some.def\\file.abc"));
+  EXPECT_STREQ(".001", BLI_path_extension("Text.001"));
+}
index b63b591b39a8608a997ba697cb8f6d4a6edc1c2c..c79afdfb92c934f660545ad3363abce8781444c9 100644 (file)
@@ -12,6 +12,7 @@ extern "C" {
 #include "BLI_utildefines.h"
 #include "BLI_string.h"
 #include "BLI_string_utf8.h"
+#include "BLI_string_utils.h"
 }
 
 using std::initializer_list;
@@ -588,3 +589,23 @@ TEST(string, StringStrncasestr)
   res = BLI_strncasestr(str_test0, "not there", 9);
   EXPECT_EQ(res, (void *)NULL);
 }
+
+
+/* BLI_string_is_decimal */
+TEST(string, StrIsDecimal)
+{
+  EXPECT_FALSE(BLI_string_is_decimal(""));
+  EXPECT_FALSE(BLI_string_is_decimal("je moeder"));
+  EXPECT_FALSE(BLI_string_is_decimal("je møder"));
+  EXPECT_FALSE(BLI_string_is_decimal("Agent 327"));
+  EXPECT_FALSE(BLI_string_is_decimal("Agent\000327"));
+  EXPECT_FALSE(BLI_string_is_decimal("\000327"));
+  EXPECT_FALSE(BLI_string_is_decimal("0x16"));
+  EXPECT_FALSE(BLI_string_is_decimal("16.4"));
+  EXPECT_FALSE(BLI_string_is_decimal("-1"));
+
+  EXPECT_TRUE(BLI_string_is_decimal("0"));
+  EXPECT_TRUE(BLI_string_is_decimal("1"));
+  EXPECT_TRUE(BLI_string_is_decimal("001"));
+  EXPECT_TRUE(BLI_string_is_decimal("11342908713948713498745980171334059871345098713405981734"));
+}