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")`
This commit is contained in:
parent
7cd6bda206
commit
f49a736ff4
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
/** \} */
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue