diff options
author | Matheus Santos <MatheusSantos> | 2022-04-07 07:32:21 +0300 |
---|---|---|
committer | Campbell Barton <campbell@blender.org> | 2022-04-07 08:17:04 +0300 |
commit | f49a736ff4023231483c7e535ca2a7f2869d641d (patch) | |
tree | 506407e67e86034329fed5c71c49435a29ec2b31 | |
parent | 7cd6bda206800218da47ee35c53066a71d25ef22 (diff) |
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")`
-rw-r--r-- | source/blender/python/intern/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna_text.c | 131 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna_text.h | 18 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna_types_capi.c | 18 | ||||
-rw-r--r-- | tests/python/CMakeLists.txt | 5 | ||||
-rw-r--r-- | tests/python/bl_pyapi_text.py | 65 |
6 files changed, 239 insertions, 0 deletions
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))", ®ion.curl, ®ion.curc, ®ion.sell, ®ion.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, ®ion.curl, ®ion.curc, ®ion.sell, ®ion.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" @@ -87,6 +88,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 * * Avoid using the RNA API because this value may change between checking its length @@ -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() |