[Bf-blender-cvs] [f49a736ff40] master: Text Editor: Get/Set region text API

Matheus Santos noreply at git.blender.org
Thu Apr 7 07:21:36 CEST 2022


Commit: f49a736ff4023231483c7e535ca2a7f2869d641d
Author: Matheus Santos
Date:   Thu Apr 7 14:32:21 2022 +1000
Branches: master
https://developer.blender.org/rBf49a736ff4023231483c7e535ca2a7f2869d641d

Text Editor: Get/Set region text API

Add the ability to get/set the selected text.

**Calling the new methods:**

- `bpy.data.texts["Text"].region_as_string()`
- `bpy.data.texts["Text"].region_from_string("Replacement")`

===================================================================

M	source/blender/python/intern/CMakeLists.txt
A	source/blender/python/intern/bpy_rna_text.c
A	source/blender/python/intern/bpy_rna_text.h
M	source/blender/python/intern/bpy_rna_types_capi.c
M	tests/python/CMakeLists.txt
A	tests/python/bl_pyapi_text.py

===================================================================

diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt
index f813a006c7e..86dc5800b67 100644
--- a/source/blender/python/intern/CMakeLists.txt
+++ b/source/blender/python/intern/CMakeLists.txt
@@ -65,6 +65,7 @@ set(SRC
   bpy_rna_gizmo.c
   bpy_rna_id_collection.c
   bpy_rna_operator.c
+  bpy_rna_text.c
   bpy_rna_types_capi.c
   bpy_rna_ui.c
   bpy_traceback.c
@@ -105,6 +106,7 @@ set(SRC
   bpy_rna_gizmo.h
   bpy_rna_id_collection.h
   bpy_rna_operator.h
+  bpy_rna_text.h
   bpy_rna_types_capi.h
   bpy_rna_ui.h
   bpy_traceback.h
diff --git a/source/blender/python/intern/bpy_rna_text.c b/source/blender/python/intern/bpy_rna_text.c
new file mode 100644
index 00000000000..44568ad30a6
--- /dev/null
+++ b/source/blender/python/intern/bpy_rna_text.c
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup pythonintern
+ *
+ * This file extends the text editor with C/Python API methods and attributes.
+ */
+
+#include <Python.h>
+
+#include "DNA_text_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "WM_api.h"
+
+#include "BKE_text.h"
+
+#include "bpy_capi_utils.h"
+#include "bpy_rna.h"
+#include "bpy_rna_text.h"
+
+/* -------------------------------------------------------------------- */
+/** \name Data structures.
+ * \{ */
+
+/**
+ * Struct representing a selection which is extracted from Python arguments.
+ */
+typedef struct TextRegion {
+  int curl;
+  int curc;
+  int sell;
+  int selc;
+} TextRegion;
+
+/* -------------------------------------------------------------------- */
+/** \name Text Editor Get / Set region text API
+ * \{ */
+
+PyDoc_STRVAR(bpy_rna_region_as_string_doc,
+             ".. method:: region_as_string(range=None)\n"
+             "\n"
+             "   :arg range: The region of text to be returned, "
+             "defaulting to the selection when no range is passed.\n"
+             "      Each int pair represents a line and column: "
+             "((start_line, start_column), (end_line, end_column))\n"
+             "      The values match Python's slicing logic "
+             "(negative values count backwards from the end, the end value is not inclusive).\n"
+             "   :type range: Two pairs of ints\n"
+             "   :return: The specified region as a string.\n"
+             "   :rtype: str.\n");
+/* Receive a Python Tuple as parameter to represent the region range. */
+static PyObject *bpy_rna_region_as_string(PyObject *self, PyObject *args)
+{
+  BPy_StructRNA *pyrna = (BPy_StructRNA *)self;
+  Text *text = pyrna->ptr.data;
+  /* Parse the region range. */
+  TextRegion region;
+  if (!PyArg_ParseTuple(
+          args, "|((ii)(ii))", &region.curl, &region.curc, &region.sell, &region.selc)) {
+    return NULL;
+  }
+
+  if (PyTuple_GET_SIZE(args) > 0) {
+    txt_sel_set(text, region.curl, region.curc, region.sell, region.selc);
+  }
+
+  /* Return an empty string if there is no selection. */
+  if (!txt_has_sel(text)) {
+    return PyUnicode_FromString("");
+  }
+  char *buf = txt_sel_to_buf(text, NULL);
+  PyObject *sel_text = PyUnicode_FromString(buf);
+  MEM_freeN(buf);
+  /* Return the selected text. */
+  return sel_text;
+}
+
+PyMethodDef BPY_rna_region_as_string_method_def = {
+    "region_as_string",
+    (PyCFunction)bpy_rna_region_as_string,
+    METH_VARARGS | METH_KEYWORDS,
+    bpy_rna_region_as_string_doc,
+};
+
+PyDoc_STRVAR(bpy_rna_region_from_string_doc,
+             ".. method:: region_from_string(body, range=None)\n"
+             "\n"
+             "   :arg body: The text to be inserted.\n"
+             "   :type body: str\n"
+             "   :arg range: The region of text to be returned, "
+             "defaulting to the selection when no range is passed.\n"
+             "      Each int pair represents a line and column: "
+             "((start_line, start_column), (end_line, end_column))\n"
+             "      The values match Python's slicing logic "
+             "(negative values count backwards from the end, the end value is not inclusive).\n"
+             "   :type range: Two pairs of ints\n");
+static PyObject *bpy_rna_region_from_string(PyObject *self, PyObject *args)
+{
+  BPy_StructRNA *pyrna = (BPy_StructRNA *)self;
+  Text *text = pyrna->ptr.data;
+
+  /* Parse the region range. */
+  const char *buf;
+  TextRegion region;
+  if (!PyArg_ParseTuple(
+          args, "s|((ii)(ii))", &buf, &region.curl, &region.curc, &region.sell, &region.selc)) {
+    return NULL;
+  }
+
+  if (PyTuple_GET_SIZE(args) > 1) {
+    txt_sel_set(text, region.curl, region.curc, region.sell, region.selc);
+  }
+
+  /* Set the selected text. */
+  txt_insert_buf(text, buf);
+  /* Update the text editor. */
+  WM_main_add_notifier(NC_TEXT | NA_EDITED, text);
+
+  Py_RETURN_NONE;
+}
+
+PyMethodDef BPY_rna_region_from_string_method_def = {
+    "region_from_string",
+    (PyCFunction)bpy_rna_region_from_string,
+    METH_VARARGS,
+    bpy_rna_region_from_string_doc,
+};
+
+/** \} */
diff --git a/source/blender/python/intern/bpy_rna_text.h b/source/blender/python/intern/bpy_rna_text.h
new file mode 100644
index 00000000000..b3854b96886
--- /dev/null
+++ b/source/blender/python/intern/bpy_rna_text.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup pythonintern
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern PyMethodDef BPY_rna_region_as_string_method_def;
+extern PyMethodDef BPY_rna_region_from_string_method_def;
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/source/blender/python/intern/bpy_rna_types_capi.c b/source/blender/python/intern/bpy_rna_types_capi.c
index 34c1a8b5a36..a5299bc1616 100644
--- a/source/blender/python/intern/bpy_rna_types_capi.c
+++ b/source/blender/python/intern/bpy_rna_types_capi.c
@@ -24,6 +24,7 @@
 #include "bpy_rna_callback.h"
 #include "bpy_rna_data.h"
 #include "bpy_rna_id_collection.h"
+#include "bpy_rna_text.h"
 #include "bpy_rna_types_capi.h"
 #include "bpy_rna_ui.h"
 
@@ -86,6 +87,16 @@ static struct PyMethodDef pyrna_operator_methods[] = {
 
 /** \} */
 
+/* -------------------------------------------------------------------- */
+/** \name Text Editor
+ * \{ */
+
+static struct PyMethodDef pyrna_text_methods[] = {
+    {NULL, NULL, 0, NULL}, /* #BPY_rna_region_as_string_method_def */
+    {NULL, NULL, 0, NULL}, /* #BPY_rna_region_from_string_method_def */
+    {NULL, NULL, 0, NULL},
+};
+
 /* -------------------------------------------------------------------- */
 /** \name Window Manager Clipboard Property
  *
@@ -228,6 +239,13 @@ void BPY_rna_types_extend_capi(void)
   /* Space */
   pyrna_struct_type_extend_capi(&RNA_Space, pyrna_space_methods, NULL);
 
+  /* Text Editor */
+  ARRAY_SET_ITEMS(pyrna_text_methods,
+                  BPY_rna_region_as_string_method_def,
+                  BPY_rna_region_from_string_method_def);
+  BLI_assert(ARRAY_SIZE(pyrna_text_methods) == 3);
+  pyrna_struct_type_extend_capi(&RNA_Text, pyrna_text_methods, NULL);
+
   /* wmOperator */
   ARRAY_SET_ITEMS(pyrna_operator_methods, BPY_rna_operator_poll_message_set_method_def);
   BLI_assert(ARRAY_SIZE(pyrna_operator_methods) == 2);
diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt
index fabac659555..a0b2e6207bf 100644
--- a/tests/python/CMakeLists.txt
+++ b/tests/python/CMakeLists.txt
@@ -116,6 +116,11 @@ add_blender_test(
   --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_prop_array.py
 )
 
+add_blender_test(
+  script_pyapi_text
+  --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_text.py
+)
+
 # ------------------------------------------------------------------------------
 # DATA MANAGEMENT TESTS
 
diff --git a/tests/python/bl_pyapi_text.py b/tests/python/bl_pyapi_text.py
new file mode 100644
index 00000000000..67e07e7d907
--- /dev/null
+++ b/tests/python/bl_pyapi_text.py
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: Apache-2.0
+
+# ./blender.bin --background -noaudio --python tests/python/bl_pyapi_text.py -- --verbose
+import bpy
+import unittest
+
+
+class TestText(unittest.TestCase):
+
+    def setUp(self):
+        self.text = bpy.data.texts.new("test_text")
+
+    def tearDown(self):
+        bpy.data.texts.remove(self.text)
+        del self.text
+
+    def test_text_new(self):
+        self.assertEqual(len(bpy.data.texts), 1)
+        self.assertEqual(self.text.name, "test_text")
+        self.assertEqual(self.text.as_string(), "\n")
+
+    def test_text_clear(self):
+        self.text.clear()
+        self.assertEqual(self.text.as_string(), "\n")
+
+    def test_text_fill(self):
+        tmp_text = (
+            "Line 1: Test line 1\n"
+            "Line 2: test line 2\n"
+            "Line 3: test line 3"
+        )
+        self.text.write(tmp_text)
+        self.assertEqual(self.text.as_string(), tmp_text + "\n")
+
+    def test_text_region_as_string(self):
+        tmp_text = (
+            "Line 1: Test line 1\n"
+            "Line 2: test line 2\n"
+            "Line 3: test line 3"
+        )
+        self.text.write(tmp_text)
+        # Get string in the middle of the text.
+        self.assertEqual(self.text.region_as_string(((1, 0), (1, -1))), "Line 2: test line 2")
+        # Big range test.
+        self.assertEqual(self.text.region_as_string(((-10000, -10000), (10000, 10000))), tmp_text)
+
+    def test_text_region_from_string(self):
+        tmp_text = (
+            "Line 1: Test line 1\n"
+            "Line 2: test line 2\n"
+            "Line 3: test line 3"
+        )
+        self.text.write(tmp_text)
+        # Set string in the middle of the text.
+        self.text.region_from_string("line 2", ((1, 0), (1, -1)))
+        self.assertEqual(self.text.as_string(), tmp_text.replace("Line 2: test line 2", "line 2") + "\n")
+        # Large range test.
+        self.text.region_from_string("New Text", ((-10000, -10000), (10000, 10000)))
+        self.assertEqual(self.text.as_string(), "New Text\n")
+
+
+if __name__ == "__main__":
+    import sys
+    sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
+    unittest.main()



More information about the Bf-blender-cvs mailing list