This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenkernel/intern/text.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

2505 lines
51 KiB
C
Raw Normal View History

2011-10-10 09:38:02 +00:00
/*
2002-10-12 11:37:38 +00:00
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
2002-10-12 11:37:38 +00:00
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
2010-02-12 13:34:04 +00:00
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2002-10-12 11:37:38 +00:00
*
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
* All rights reserved.
*/
/** \file
* \ingroup bke
2011-02-27 20:40:57 +00:00
*/
#include <stdlib.h> /* abort */
2002-10-12 11:37:38 +00:00
#include <string.h> /* strstr */
#include <sys/stat.h>
#include <sys/types.h>
#include <wctype.h>
2002-10-12 11:37:38 +00:00
#include "MEM_guardedalloc.h"
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_string_cursor_utf8.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
2002-10-12 11:37:38 +00:00
#include "BLT_translation.h"
#include "DNA_constraint_types.h"
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
Giant commit! A full detailed description of this will be done later... is several days of work. Here's a summary: Render: - Full cleanup of render code, removing *all* globals and bad level calls all over blender. Render module is now not called abusive anymore - API-fied calls to rendering - Full recode of internal render pipeline. Is now rendering tiles by default, prepared for much smarter 'bucket' render later. - Each thread now can render a full part - Renders were tested with 4 threads, goes fine, apart from some lookup tables in softshadow and AO still - Rendering is prepared to do multiple layers and passes - No single 32 bits trick in render code anymore, all 100% floats now. Writing images/movies - moved writing images to blender kernel (bye bye 'schrijfplaatje'!) - made a new Movie handle system, also in kernel. This will enable much easier use of movies in Blender PreviewRender: - Using new render API, previewrender (in buttons) now uses regular render code to generate images. - new datafile 'preview.blend.c' has the preview scenes in it - previews get rendered in exact displayed size (1 pixel = 1 pixel) 3D Preview render - new; press Pkey in 3d window, for a panel that continuously renders (pkey is for games, i know... but we dont do that in orange now!) - this render works nearly identical to buttons-preview render, so it stops rendering on any event (mouse, keyboard, etc) - on moving/scaling the panel, the render code doesn't recreate all geometry - same for shifting/panning view - all other operations (now) regenerate the full render database still. - this is WIP... but big fun, especially for simple scenes! Compositor - Using same node system as now in use for shaders, you can composit images - works pretty straightforward... needs much more options/tools and integration with rendering still - is not threaded yet, nor is so smart to only recalculate changes... will be done soon! - the "Render Result" node will get all layers/passes as output sockets - The "Output" node renders to a builtin image, which you can view in the Image window. (yes, output nodes to render-result, and to files, is on the list!) The Bad News - "Unified Render" is removed. It might come back in some stage, but this system should be built from scratch. I can't really understand this code... I expect it is not much needed, especially with advanced layer/passes control - Panorama render, Field render, Motion blur, is not coded yet... (I had to recode every single feature in render, so...!) - Lens Flare is also not back... needs total revision, might become composit effect though (using zbuffer for visibility) - Part render is gone! (well, thats obvious, its default now). - The render window is only restored with limited functionality... I am going to check first the option to render to a Image window, so Blender can become a true single-window application. :) For example, the 'Spare render buffer' (jkey) doesnt work. - Render with border, now default creates a smaller image - No zbuffers are written yet... on the todo! - Scons files and MSVC will need work to get compiling again OK... thats what I can quickly recall. Now go compiling!
2006-01-23 22:05:47 +00:00
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
2002-10-12 11:37:38 +00:00
#include "DNA_text_types.h"
#include "DNA_userdef_types.h"
2002-10-12 11:37:38 +00:00
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
2002-10-12 11:37:38 +00:00
#include "BKE_main.h"
#include "BKE_node.h"
#include "BKE_text.h"
#include "BLO_read_write.h"
#ifdef WITH_PYTHON
# include "BPY_extern.h"
#endif
/* -------------------------------------------------------------------- */
/** \name Prototypes
* \{ */
2002-10-12 11:37:38 +00:00
static void txt_pop_first(Text *text);
static void txt_pop_last(Text *text);
static void txt_delete_line(Text *text, TextLine *line);
static void txt_delete_sel(Text *text);
static void txt_make_dirty(Text *text);
2002-10-12 11:37:38 +00:00
/** \} */
/* -------------------------------------------------------------------- */
/** \name Text Data-Block
* \{ */
static void text_init_data(ID *id)
{
Text *text = (Text *)id;
TextLine *tmp;
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(text, id));
text->filepath = NULL;
text->flags = TXT_ISDIRTY | TXT_ISMEM;
if ((U.flag & USER_TXT_TABSTOSPACES_DISABLE) == 0) {
text->flags |= TXT_TABSTOSPACES;
}
BLI_listbase_clear(&text->lines);
tmp = (TextLine *)MEM_mallocN(sizeof(TextLine), "textline");
tmp->line = (char *)MEM_mallocN(1, "textline_string");
tmp->format = NULL;
tmp->line[0] = 0;
tmp->len = 0;
tmp->next = NULL;
tmp->prev = NULL;
BLI_addhead(&text->lines, tmp);
text->curl = text->lines.first;
text->curc = 0;
text->sell = text->lines.first;
text->selc = 0;
}
2002-10-12 11:37:38 +00:00
/**
* Only copy internal data of Text ID from source
* to already allocated/initialized destination.
* You probably never want to use that directly,
* use #BKE_id_copy or #BKE_id_copy_ex for typical needs.
*
* WARNING! This function will not handle ID user count!
*
* \param flag: Copying options (see BKE_lib_id.h's LIB_ID_COPY_... flags for more).
*/
static void text_copy_data(Main *UNUSED(bmain),
ID *id_dst,
const ID *id_src,
const int UNUSED(flag))
2002-10-12 11:37:38 +00:00
{
Text *text_dst = (Text *)id_dst;
const Text *text_src = (Text *)id_src;
/* File name can be NULL. */
if (text_src->filepath) {
text_dst->filepath = BLI_strdup(text_src->filepath);
}
text_dst->flags |= TXT_ISDIRTY;
BLI_listbase_clear(&text_dst->lines);
text_dst->curl = text_dst->sell = NULL;
text_dst->compiled = NULL;
/* Walk down, reconstructing. */
LISTBASE_FOREACH (TextLine *, line_src, &text_src->lines) {
TextLine *line_dst = MEM_mallocN(sizeof(*line_dst), __func__);
line_dst->line = BLI_strdup(line_src->line);
line_dst->format = NULL;
line_dst->len = line_src->len;
BLI_addtail(&text_dst->lines, line_dst);
}
text_dst->curl = text_dst->sell = text_dst->lines.first;
text_dst->curc = text_dst->selc = 0;
}
ID-Remap - Step one: core work (cleanup and rework of generic ID datablock handling). This commit changes a lot of how IDs are handled internally, especially the unlinking/freeing processes. So far, this was very fuzy, to summarize cleanly deleting or replacing a datablock was pretty much impossible, except for a few special cases. Also, unlinking was handled by each datatype, in a rather messy and prone-to-errors way (quite a few ID usages were missed or wrongly handled that way). One of the main goal of id-remap branch was to cleanup this, and fatorize ID links handling by using library_query utils to allow generic handling of those, which is now the case (now, generic ID links handling is only "knwon" from readfile.c and library_query.c). This commit also adds backends to allow live replacement and deletion of datablocks in Blender (so-called 'remapping' process, where we replace all usages of a given ID pointer by a new one, or NULL one in case of unlinking). This will allow nice new features, like ability to easily reload or relocate libraries, real immediate deletion of datablocks in blender, replacement of one datablock by another, etc. Some of those are for next commits. A word of warning: this commit is highly risky, because it affects potentially a lot in Blender core. Though it was tested rather deeply, being totally impossible to check all possible ID usage cases, it's likely there are some remaining issues and bugs in new code... Please report them! ;) Review task: D2027 (https://developer.blender.org/D2027). Reviewed by campbellbarton, thanks a bunch.
2016-06-22 17:29:38 +02:00
/** Free (or release) any data used by this text (does not free the text itself). */
static void text_free_data(ID *id)
{
ID-Remap - Step one: core work (cleanup and rework of generic ID datablock handling). This commit changes a lot of how IDs are handled internally, especially the unlinking/freeing processes. So far, this was very fuzy, to summarize cleanly deleting or replacing a datablock was pretty much impossible, except for a few special cases. Also, unlinking was handled by each datatype, in a rather messy and prone-to-errors way (quite a few ID usages were missed or wrongly handled that way). One of the main goal of id-remap branch was to cleanup this, and fatorize ID links handling by using library_query utils to allow generic handling of those, which is now the case (now, generic ID links handling is only "knwon" from readfile.c and library_query.c). This commit also adds backends to allow live replacement and deletion of datablocks in Blender (so-called 'remapping' process, where we replace all usages of a given ID pointer by a new one, or NULL one in case of unlinking). This will allow nice new features, like ability to easily reload or relocate libraries, real immediate deletion of datablocks in blender, replacement of one datablock by another, etc. Some of those are for next commits. A word of warning: this commit is highly risky, because it affects potentially a lot in Blender core. Though it was tested rather deeply, being totally impossible to check all possible ID usage cases, it's likely there are some remaining issues and bugs in new code... Please report them! ;) Review task: D2027 (https://developer.blender.org/D2027). Reviewed by campbellbarton, thanks a bunch.
2016-06-22 17:29:38 +02:00
/* No animdata here. */
Text *text = (Text *)id;
ID-Remap - Step one: core work (cleanup and rework of generic ID datablock handling). This commit changes a lot of how IDs are handled internally, especially the unlinking/freeing processes. So far, this was very fuzy, to summarize cleanly deleting or replacing a datablock was pretty much impossible, except for a few special cases. Also, unlinking was handled by each datatype, in a rather messy and prone-to-errors way (quite a few ID usages were missed or wrongly handled that way). One of the main goal of id-remap branch was to cleanup this, and fatorize ID links handling by using library_query utils to allow generic handling of those, which is now the case (now, generic ID links handling is only "knwon" from readfile.c and library_query.c). This commit also adds backends to allow live replacement and deletion of datablocks in Blender (so-called 'remapping' process, where we replace all usages of a given ID pointer by a new one, or NULL one in case of unlinking). This will allow nice new features, like ability to easily reload or relocate libraries, real immediate deletion of datablocks in blender, replacement of one datablock by another, etc. Some of those are for next commits. A word of warning: this commit is highly risky, because it affects potentially a lot in Blender core. Though it was tested rather deeply, being totally impossible to check all possible ID usage cases, it's likely there are some remaining issues and bugs in new code... Please report them! ;) Review task: D2027 (https://developer.blender.org/D2027). Reviewed by campbellbarton, thanks a bunch.
2016-06-22 17:29:38 +02:00
BKE_text_free_lines(text);
2002-10-12 11:37:38 +00:00
MEM_SAFE_FREE(text->filepath);
#ifdef WITH_PYTHON
ID-Remap - Step one: core work (cleanup and rework of generic ID datablock handling). This commit changes a lot of how IDs are handled internally, especially the unlinking/freeing processes. So far, this was very fuzy, to summarize cleanly deleting or replacing a datablock was pretty much impossible, except for a few special cases. Also, unlinking was handled by each datatype, in a rather messy and prone-to-errors way (quite a few ID usages were missed or wrongly handled that way). One of the main goal of id-remap branch was to cleanup this, and fatorize ID links handling by using library_query utils to allow generic handling of those, which is now the case (now, generic ID links handling is only "knwon" from readfile.c and library_query.c). This commit also adds backends to allow live replacement and deletion of datablocks in Blender (so-called 'remapping' process, where we replace all usages of a given ID pointer by a new one, or NULL one in case of unlinking). This will allow nice new features, like ability to easily reload or relocate libraries, real immediate deletion of datablocks in blender, replacement of one datablock by another, etc. Some of those are for next commits. A word of warning: this commit is highly risky, because it affects potentially a lot in Blender core. Though it was tested rather deeply, being totally impossible to check all possible ID usage cases, it's likely there are some remaining issues and bugs in new code... Please report them! ;) Review task: D2027 (https://developer.blender.org/D2027). Reviewed by campbellbarton, thanks a bunch.
2016-06-22 17:29:38 +02:00
BPY_text_free_code(text);
#endif
2002-10-12 11:37:38 +00:00
}
static void text_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
if (id->us < 1 && !BLO_write_is_undo(writer)) {
return;
}
Text *text = (Text *)id;
/* Note: we are clearing local temp data here, *not* the flag in the actual 'real' ID. */
if ((text->flags & TXT_ISMEM) && (text->flags & TXT_ISEXT)) {
text->flags &= ~TXT_ISEXT;
}
/* Clean up, important in undo case to reduce false detection of changed datablocks. */
text->compiled = NULL;
/* write LibData */
BLO_write_id_struct(writer, Text, id_address, &text->id);
BKE_id_blend_write(writer, &text->id);
if (text->filepath) {
BLO_write_string(writer, text->filepath);
}
if (!(text->flags & TXT_ISEXT)) {
/* now write the text data, in two steps for optimization in the readfunction */
LISTBASE_FOREACH (TextLine *, tmp, &text->lines) {
BLO_write_struct(writer, TextLine, tmp);
}
LISTBASE_FOREACH (TextLine *, tmp, &text->lines) {
BLO_write_raw(writer, tmp->len + 1, tmp->line);
}
}
}
static void text_blend_read_data(BlendDataReader *reader, ID *id)
{
Text *text = (Text *)id;
BLO_read_data_address(reader, &text->filepath);
text->compiled = NULL;
#if 0
if (text->flags & TXT_ISEXT) {
BKE_text_reload(text);
}
/* else { */
#endif
BLO_read_list(reader, &text->lines);
BLO_read_data_address(reader, &text->curl);
BLO_read_data_address(reader, &text->sell);
LISTBASE_FOREACH (TextLine *, ln, &text->lines) {
BLO_read_data_address(reader, &ln->line);
ln->format = NULL;
if (ln->len != (int)strlen(ln->line)) {
printf("Error loading text, line lengths differ\n");
ln->len = strlen(ln->line);
}
}
text->flags = (text->flags) & ~TXT_ISEXT;
}
IDTypeInfo IDType_ID_TXT = {
.id_code = ID_TXT,
.id_filter = FILTER_ID_TXT,
.main_listbase_index = INDEX_ID_TXT,
.struct_size = sizeof(Text),
.name = "Text",
.name_plural = "texts",
.translation_context = BLT_I18NCONTEXT_ID_TEXT,
.flags = IDTYPE_FLAGS_NO_ANIMDATA,
.init_data = text_init_data,
.copy_data = text_copy_data,
.free_data = text_free_data,
.make_local = NULL,
.foreach_id = NULL,
.foreach_cache = NULL,
.owner_get = NULL,
.blend_write = text_blend_write,
.blend_read_data = text_blend_read_data,
.blend_read_lib = NULL,
.blend_read_expand = NULL,
.blend_read_undo_preserve = NULL,
.lib_override_apply_post = NULL,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Text Add, Free, Validation
* \{ */
2002-10-12 11:37:38 +00:00
/**
* \note caller must handle `compiled` member.
*/
void BKE_text_free_lines(Text *text)
{
for (TextLine *tmp = text->lines.first, *tmp_next; tmp; tmp = tmp_next) {
tmp_next = tmp->next;
MEM_freeN(tmp->line);
if (tmp->format) {
MEM_freeN(tmp->format);
}
MEM_freeN(tmp);
}
2002-10-12 11:37:38 +00:00
BLI_listbase_clear(&text->lines);
2018-06-17 17:05:51 +02:00
text->curl = text->sell = NULL;
}
Text *BKE_text_add(Main *bmain, const char *name)
{
Text *ta;
ta = BKE_id_new(bmain, ID_TXT, name);
/* Texts have no users by default... Set the fake user flag to ensure that this text block
* doesn't get deleted by default when cleaning up data blocks. */
id_us_min(&ta->id);
id_fake_user_set(&ta->id);
2002-10-12 11:37:38 +00:00
return ta;
}
/* this function replaces extended ascii characters */
/* to a valid utf-8 sequences */
int txt_extended_ascii_as_utf8(char **str)
{
ptrdiff_t bad_char, i = 0;
const ptrdiff_t length = (ptrdiff_t)strlen(*str);
int added = 0;
while ((*str)[i]) {
if ((bad_char = BLI_utf8_invalid_byte(*str + i, length - i)) == -1) {
break;
}
added++;
i += bad_char + 1;
}
2018-06-17 17:05:51 +02:00
if (added != 0) {
char *newstr = MEM_mallocN(length + added + 1, "text_line");
ptrdiff_t mi = 0;
i = 0;
2018-06-17 17:05:51 +02:00
while ((*str)[i]) {
if ((bad_char = BLI_utf8_invalid_byte((*str) + i, length - i)) == -1) {
memcpy(newstr + mi, (*str) + i, length - i + 1);
break;
}
2018-06-17 17:05:51 +02:00
memcpy(newstr + mi, (*str) + i, bad_char);
BLI_str_utf8_from_unicode((*str)[i + bad_char], newstr + mi + bad_char);
i += bad_char + 1;
mi += bad_char + 2;
}
newstr[length + added] = '\0';
MEM_freeN(*str);
*str = newstr;
}
2018-06-17 17:05:51 +02:00
return added;
}
2002-10-12 11:37:38 +00:00
// this function removes any control characters from
// a textline and fixes invalid utf-8 sequences
2002-10-12 11:37:38 +00:00
static void cleanup_textline(TextLine *tl)
2002-10-12 11:37:38 +00:00
{
int i;
for (i = 0; i < tl->len; i++) {
2002-10-12 11:37:38 +00:00
if (tl->line[i] < ' ' && tl->line[i] != '\t') {
memmove(tl->line + i, tl->line + i + 1, tl->len - i);
tl->len--;
i--;
}
}
tl->len += txt_extended_ascii_as_utf8(&tl->line);
2002-10-12 11:37:38 +00:00
}
/**
* used for load and reload (unlike txt_insert_buf)
* assumes all fields are empty
*/
static void text_from_buf(Text *text, const unsigned char *buffer, const int len)
{
int i, llen, lines_count;
BLI_assert(BLI_listbase_is_empty(&text->lines));
llen = 0;
lines_count = 0;
for (i = 0; i < len; i++) {
if (buffer[i] == '\n') {
TextLine *tmp;
tmp = (TextLine *)MEM_mallocN(sizeof(TextLine), "textline");
tmp->line = (char *)MEM_mallocN(llen + 1, "textline_string");
tmp->format = NULL;
if (llen) {
memcpy(tmp->line, &buffer[i - llen], llen);
}
tmp->line[llen] = 0;
tmp->len = llen;
cleanup_textline(tmp);
BLI_addtail(&text->lines, tmp);
lines_count += 1;
llen = 0;
continue;
}
llen++;
}
/* create new line in cases:
* - rest of line (if last line in file hasn't got \n terminator).
* in this case content of such line would be used to fill text line buffer
* - file is empty. in this case new line is needed to start editing from.
* - last character in buffer is \n. in this case new line is needed to
* deal with newline at end of file. (see T28087) (sergey) */
if (llen != 0 || lines_count == 0 || buffer[len - 1] == '\n') {
TextLine *tmp;
tmp = (TextLine *)MEM_mallocN(sizeof(TextLine), "textline");
tmp->line = (char *)MEM_mallocN(llen + 1, "textline_string");
tmp->format = NULL;
if (llen) {
memcpy(tmp->line, &buffer[i - llen], llen);
}
tmp->line[llen] = 0;
tmp->len = llen;
cleanup_textline(tmp);
BLI_addtail(&text->lines, tmp);
/* lines_count += 1; */ /* UNUSED */
}
text->curl = text->sell = text->lines.first;
text->curc = text->selc = 0;
}
2015-06-05 11:46:01 +10:00
bool BKE_text_reload(Text *text)
2002-10-12 11:37:38 +00:00
{
unsigned char *buffer;
size_t buffer_len;
char filepath_abs[FILE_MAX];
BLI_stat_t st;
2002-10-12 11:37:38 +00:00
if (!text->filepath) {
2015-06-05 11:46:01 +10:00
return false;
}
BLI_strncpy(filepath_abs, text->filepath, FILE_MAX);
BLI_path_abs(filepath_abs, ID_BLEND_PATH_FROM_GLOBAL(&text->id));
2018-06-17 17:05:51 +02:00
buffer = BLI_file_read_text_as_mem(filepath_abs, 0, &buffer_len);
if (buffer == NULL) {
2015-06-05 11:46:01 +10:00
return false;
}
2002-10-12 11:37:38 +00:00
/* free memory: */
BKE_text_free_lines(text);
txt_make_dirty(text);
2002-10-12 11:37:38 +00:00
/* clear undo buffer */
if (BLI_stat(filepath_abs, &st) != -1) {
text->mtime = st.st_mtime;
}
else {
text->mtime = 0;
}
2002-10-12 11:37:38 +00:00
text_from_buf(text, buffer, buffer_len);
2002-10-12 11:37:38 +00:00
MEM_freeN(buffer);
2015-06-05 11:46:01 +10:00
return true;
2002-10-12 11:37:38 +00:00
}
/**
* Load a text file.
*
* \param is_internal: If \a true, this text data-block only exists in memory,
* not as a file on disk.
*
* \note text data-blocks have no real user but have 'fake user' enabled by default
*/
Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const bool is_internal)
2002-10-12 11:37:38 +00:00
{
unsigned char *buffer;
size_t buffer_len;
2002-10-12 11:37:38 +00:00
Text *ta;
char filepath_abs[FILE_MAX];
BLI_stat_t st;
2002-10-12 11:37:38 +00:00
BLI_strncpy(filepath_abs, file, FILE_MAX);
if (relpath) { /* can be NULL (bg mode) */
BLI_path_abs(filepath_abs, relpath);
}
2018-06-17 17:05:51 +02:00
buffer = BLI_file_read_text_as_mem(filepath_abs, 0, &buffer_len);
if (buffer == NULL) {
return NULL;
2015-06-05 11:46:01 +10:00
}
ta = BKE_libblock_alloc(bmain, ID_TXT, BLI_path_basename(filepath_abs), 0);
id_us_min(&ta->id);
id_fake_user_set(&ta->id);
2002-10-12 11:37:38 +00:00
BLI_listbase_clear(&ta->lines);
ta->curl = ta->sell = NULL;
if ((U.flag & USER_TXT_TABSTOSPACES_DISABLE) == 0) {
ta->flags = TXT_TABSTOSPACES;
}
if (is_internal == false) {
ta->filepath = MEM_mallocN(strlen(file) + 1, "text_name");
strcpy(ta->filepath, file);
}
else {
ta->flags |= TXT_ISMEM | TXT_ISDIRTY;
}
2002-10-12 11:37:38 +00:00
/* clear undo buffer */
if (BLI_stat(filepath_abs, &st) != -1) {
ta->mtime = st.st_mtime;
}
else {
ta->mtime = 0;
}
2018-06-17 17:05:51 +02:00
text_from_buf(ta, buffer, buffer_len);
2018-06-17 17:05:51 +02:00
MEM_freeN(buffer);
2002-10-12 11:37:38 +00:00
return ta;
}
/**
* Load a text file.
*
* \note Text data-blocks have no user by default, only the 'real user' flag.
*/
Text *BKE_text_load(Main *bmain, const char *file, const char *relpath)
{
return BKE_text_load_ex(bmain, file, relpath, false);
}
void BKE_text_clear(Text *text) /* called directly from rna */
{
txt_sel_all(text);
txt_delete_sel(text);
txt_make_dirty(text);
}
void BKE_text_write(Text *text, const char *str) /* called directly from rna */
{
txt_insert_buf(text, str);
txt_move_eof(text, 0);
txt_make_dirty(text);
}
/* returns 0 if file on disk is the same or Text is in memory only
* returns 1 if file has been modified on disk since last local edit
* returns 2 if file on disk has been deleted
* -1 is returned if an error occurs */
int BKE_text_file_modified_check(Text *text)
{
BLI_stat_t st;
int result;
char file[FILE_MAX];
if (!text->filepath) {
return 0;
}
BLI_strncpy(file, text->filepath, FILE_MAX);
BLI_path_abs(file, ID_BLEND_PATH_FROM_GLOBAL(&text->id));
if (!BLI_exists(file)) {
return 2;
}
result = BLI_stat(file, &st);
if (result == -1) {
return -1;
}
if ((st.st_mode & S_IFMT) != S_IFREG) {
return -1;
}
if (st.st_mtime > text->mtime) {
return 1;
}
return 0;
}
void BKE_text_file_modified_ignore(Text *text)
{
BLI_stat_t st;
int result;
char file[FILE_MAX];
if (!text->filepath) {
2014-10-06 12:23:47 +02:00
return;
}
BLI_strncpy(file, text->filepath, FILE_MAX);
BLI_path_abs(file, ID_BLEND_PATH_FROM_GLOBAL(&text->id));
if (!BLI_exists(file)) {
return;
}
result = BLI_stat(file, &st);
if (result == -1 || (st.st_mode & S_IFMT) != S_IFREG) {
return;
}
text->mtime = st.st_mtime;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Editing Utility Functions
* \{ */
2002-10-12 11:37:38 +00:00
static void make_new_line(TextLine *line, char *newline)
2002-10-12 11:37:38 +00:00
{
if (line->line) {
2005-05-18 23:54:56 +00:00
MEM_freeN(line->line);
}
if (line->format) {
2005-05-18 23:54:56 +00:00
MEM_freeN(line->format);
}
2018-06-17 17:05:51 +02:00
line->line = newline;
line->len = strlen(newline);
line->format = NULL;
2002-10-12 11:37:38 +00:00
}
static TextLine *txt_new_line(const char *str)
2002-10-12 11:37:38 +00:00
{
TextLine *tmp;
if (!str) {
str = "";
}
2018-06-17 17:05:51 +02:00
tmp = (TextLine *)MEM_mallocN(sizeof(TextLine), "textline");
tmp->line = MEM_mallocN(strlen(str) + 1, "textline_string");
tmp->format = NULL;
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
strcpy(tmp->line, str);
2018-06-17 17:05:51 +02:00
tmp->len = strlen(str);
tmp->next = tmp->prev = NULL;
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
return tmp;
}
static TextLine *txt_new_linen(const char *str, int n)
2002-10-12 11:37:38 +00:00
{
TextLine *tmp;
tmp = (TextLine *)MEM_mallocN(sizeof(TextLine), "textline");
tmp->line = MEM_mallocN(n + 1, "textline_string");
tmp->format = NULL;
2018-06-17 17:05:51 +02:00
BLI_strncpy(tmp->line, (str) ? str : "", n + 1);
2018-06-17 17:05:51 +02:00
tmp->len = strlen(tmp->line);
tmp->next = tmp->prev = NULL;
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
return tmp;
}
void txt_clean_text(Text *text)
2018-06-17 17:05:51 +02:00
{
2002-10-12 11:37:38 +00:00
TextLine **top, **bot;
2014-10-06 12:23:47 +02:00
2002-10-12 11:37:38 +00:00
if (!text->lines.first) {
if (text->lines.last) {
text->lines.first = text->lines.last;
}
else {
text->lines.first = text->lines.last = txt_new_line(NULL);
}
}
2018-06-17 17:05:51 +02:00
if (!text->lines.last) {
text->lines.last = text->lines.first;
}
2002-10-12 11:37:38 +00:00
top = (TextLine **)&text->lines.first;
bot = (TextLine **)&text->lines.last;
2018-06-17 17:05:51 +02:00
while ((*top)->prev) {
*top = (*top)->prev;
}
while ((*bot)->next) {
*bot = (*bot)->next;
}
2002-10-12 11:37:38 +00:00
if (!text->curl) {
if (text->sell) {
text->curl = text->sell;
}
else {
text->curl = text->lines.first;
}
text->curc = 0;
2002-10-12 11:37:38 +00:00
}
if (!text->sell) {
text->sell = text->curl;
text->selc = 0;
2002-10-12 11:37:38 +00:00
}
}
int txt_get_span(TextLine *from, TextLine *to)
2002-10-12 11:37:38 +00:00
{
int ret = 0;
TextLine *tmp = from;
if (!to || !from) {
2002-10-12 11:37:38 +00:00
return 0;
}
if (from == to) {
return 0;
}
2002-10-12 11:37:38 +00:00
/* Look forwards */
while (tmp) {
if (tmp == to) {
2002-10-12 11:37:38 +00:00
return ret;
}
2002-10-12 11:37:38 +00:00
ret++;
tmp = tmp->next;
2002-10-12 11:37:38 +00:00
}
2002-10-12 11:37:38 +00:00
/* Look backwards */
if (!tmp) {
tmp = from;
ret = 0;
while (tmp) {
if (tmp == to) {
2002-10-12 11:37:38 +00:00
break;
}
2002-10-12 11:37:38 +00:00
ret--;
tmp = tmp->prev;
2002-10-12 11:37:38 +00:00
}
if (!tmp) {
ret = 0;
}
2002-10-12 11:37:38 +00:00
}
return ret;
2002-10-12 11:37:38 +00:00
}
static void txt_make_dirty(Text *text)
2002-10-12 11:37:38 +00:00
{
text->flags |= TXT_ISDIRTY;
#ifdef WITH_PYTHON
if (text->compiled) {
BPY_text_free_code(text);
}
#endif
2002-10-12 11:37:38 +00:00
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cursor Utility Functions
* \{ */
2002-10-12 11:37:38 +00:00
static void txt_curs_cur(Text *text, TextLine ***linep, int **charp)
2002-10-12 11:37:38 +00:00
{
*linep = &text->curl;
*charp = &text->curc;
2002-10-12 11:37:38 +00:00
}
static void txt_curs_sel(Text *text, TextLine ***linep, int **charp)
2002-10-12 11:37:38 +00:00
{
*linep = &text->sell;
*charp = &text->selc;
2002-10-12 11:37:38 +00:00
}
bool txt_cursor_is_line_start(Text *text)
{
return (text->selc == 0);
}
bool txt_cursor_is_line_end(Text *text)
{
return (text->selc == text->sell->len);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cursor Movement Functions
*
* \note If the user moves the cursor the space containing that cursor should be popped
* See #txt_pop_first, #txt_pop_last
* Other space-types retain their own top location.
* \{ */
void txt_move_up(Text *text, const bool sel)
2002-10-12 11:37:38 +00:00
{
TextLine **linep;
2012-01-22 17:26:56 +00:00
int *charp;
2002-10-12 11:37:38 +00:00
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
else {
txt_pop_first(text);
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
return;
}
2018-06-17 17:05:51 +02:00
if ((*linep)->prev) {
int column = BLI_str_utf8_offset_to_column((*linep)->line, *charp);
*linep = (*linep)->prev;
*charp = BLI_str_utf8_offset_from_column((*linep)->line, column);
}
else {
txt_move_bol(text, sel);
2002-10-12 11:37:38 +00:00
}
if (!sel) {
txt_pop_sel(text);
}
2002-10-12 11:37:38 +00:00
}
void txt_move_down(Text *text, const bool sel)
2002-10-12 11:37:38 +00:00
{
TextLine **linep;
2012-01-22 17:26:56 +00:00
int *charp;
2014-10-06 12:23:47 +02:00
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
2002-10-12 11:37:38 +00:00
else {
txt_pop_last(text);
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
2002-10-12 11:37:38 +00:00
return;
}
2002-10-12 11:37:38 +00:00
if ((*linep)->next) {
int column = BLI_str_utf8_offset_to_column((*linep)->line, *charp);
*linep = (*linep)->next;
*charp = BLI_str_utf8_offset_from_column((*linep)->line, column);
}
else {
txt_move_eol(text, sel);
2002-10-12 11:37:38 +00:00
}
if (!sel) {
txt_pop_sel(text);
}
2002-10-12 11:37:38 +00:00
}
int txt_calc_tab_left(TextLine *tl, int ch)
{
/* do nice left only if there are only spaces */
int tabsize = (ch < TXT_TABSIZE) ? ch : TXT_TABSIZE;
for (int i = 0; i < ch; i++) {
if (tl->line[i] != ' ') {
tabsize = 0;
break;
}
}
/* if in the middle of the space-tab */
if (tabsize && ch % TXT_TABSIZE != 0) {
tabsize = (ch % TXT_TABSIZE);
}
return tabsize;
}
int txt_calc_tab_right(TextLine *tl, int ch)
{
if (tl->line[ch] == ' ') {
int i;
for (i = 0; i < ch; i++) {
if (tl->line[i] != ' ') {
return 0;
}
}
int tabsize = (ch) % TXT_TABSIZE + 1;
for (i = ch + 1; tl->line[i] == ' ' && tabsize < TXT_TABSIZE; i++) {
tabsize++;
}
return i - ch;
}
return 0;
}
void txt_move_left(Text *text, const bool sel)
2002-10-12 11:37:38 +00:00
{
TextLine **linep;
int *charp;
int tabsize = 0;
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
2002-10-12 11:37:38 +00:00
else {
txt_pop_first(text);
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
2002-10-12 11:37:38 +00:00
return;
}
if (*charp == 0) {
if ((*linep)->prev) {
txt_move_up(text, sel);
*charp = (*linep)->len;
}
}
else {
/* do nice left only if there are only spaces */
/* #TXT_TABSIZE hard-coded in DNA_text_types.h */
if (text->flags & TXT_TABSTOSPACES) {
tabsize = txt_calc_tab_left(*linep, *charp);
}
if (tabsize) {
(*charp) -= tabsize;
}
else {
const char *prev = BLI_str_prev_char_utf8((*linep)->line + *charp);
*charp = prev - (*linep)->line;
2002-10-12 11:37:38 +00:00
}
}
if (!sel) {
txt_pop_sel(text);
}
2002-10-12 11:37:38 +00:00
}
void txt_move_right(Text *text, const bool sel)
2002-10-12 11:37:38 +00:00
{
TextLine **linep;
int *charp;
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
2002-10-12 11:37:38 +00:00
else {
txt_pop_last(text);
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
2002-10-12 11:37:38 +00:00
return;
}
if (*charp == (*linep)->len) {
2002-10-12 11:37:38 +00:00
if ((*linep)->next) {
txt_move_down(text, sel);
*charp = 0;
}
}
else {
/* do nice right only if there are only spaces */
/* spaces hardcoded in DNA_text_types.h */
int tabsize = 0;
if (text->flags & TXT_TABSTOSPACES) {
tabsize = txt_calc_tab_right(*linep, *charp);
}
if (tabsize) {
(*charp) += tabsize;
}
else {
(*charp) += BLI_str_utf8_size((*linep)->line + *charp);
}
2002-10-12 11:37:38 +00:00
}
if (!sel) {
txt_pop_sel(text);
}
2002-10-12 11:37:38 +00:00
}
void txt_jump_left(Text *text, const bool sel, const bool use_init_step)
{
TextLine **linep;
int *charp;
2014-10-06 12:23:47 +02:00
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
else {
txt_pop_first(text);
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
return;
}
BLI_str_cursor_step_utf8(
(*linep)->line, (*linep)->len, charp, STRCUR_DIR_PREV, STRCUR_JUMP_DELIM, use_init_step);
2018-06-17 17:05:51 +02:00
if (!sel) {
txt_pop_sel(text);
}
}
void txt_jump_right(Text *text, const bool sel, const bool use_init_step)
{
TextLine **linep;
int *charp;
2014-10-06 12:23:47 +02:00
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
else {
txt_pop_last(text);
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
return;
}
2018-06-17 17:05:51 +02:00
BLI_str_cursor_step_utf8(
(*linep)->line, (*linep)->len, charp, STRCUR_DIR_NEXT, STRCUR_JUMP_DELIM, use_init_step);
2018-06-17 17:05:51 +02:00
if (!sel) {
txt_pop_sel(text);
}
}
void txt_move_bol(Text *text, const bool sel)
2002-10-12 11:37:38 +00:00
{
TextLine **linep;
int *charp;
2014-10-06 12:23:47 +02:00
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
else {
2002-10-12 11:37:38 +00:00
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
2002-10-12 11:37:38 +00:00
return;
}
2018-06-17 17:05:51 +02:00
*charp = 0;
2002-10-12 11:37:38 +00:00
if (!sel) {
txt_pop_sel(text);
}
2002-10-12 11:37:38 +00:00
}
void txt_move_eol(Text *text, const bool sel)
2002-10-12 11:37:38 +00:00
{
TextLine **linep;
int *charp;
2014-10-06 12:23:47 +02:00
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
else {
2002-10-12 11:37:38 +00:00
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
2002-10-12 11:37:38 +00:00
return;
}
*charp = (*linep)->len;
2002-10-12 11:37:38 +00:00
if (!sel) {
txt_pop_sel(text);
}
2002-10-12 11:37:38 +00:00
}
void txt_move_bof(Text *text, const bool sel)
2002-10-12 11:37:38 +00:00
{
TextLine **linep;
int *charp;
2014-10-06 12:23:47 +02:00
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
else {
2002-10-12 11:37:38 +00:00
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
2002-10-12 11:37:38 +00:00
return;
}
2002-10-12 11:37:38 +00:00
*linep = text->lines.first;
*charp = 0;
2002-10-12 11:37:38 +00:00
if (!sel) {
txt_pop_sel(text);
}
2002-10-12 11:37:38 +00:00
}
void txt_move_eof(Text *text, const bool sel)
2002-10-12 11:37:38 +00:00
{
TextLine **linep;
int *charp;
2014-10-06 12:23:47 +02:00
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
else {
2002-10-12 11:37:38 +00:00
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
2002-10-12 11:37:38 +00:00
return;
}
2002-10-12 11:37:38 +00:00
*linep = text->lines.last;
*charp = (*linep)->len;
2002-10-12 11:37:38 +00:00
if (!sel) {
txt_pop_sel(text);
}
2002-10-12 11:37:38 +00:00
}
void txt_move_toline(Text *text, unsigned int line, const bool sel)
{
txt_move_to(text, line, 0, sel);
}
/* Moves to a certain byte in a line, not a certain utf8-character! */
void txt_move_to(Text *text, unsigned int line, unsigned int ch, const bool sel)
2002-10-12 11:37:38 +00:00
{
TextLine **linep;
int *charp;
2002-10-12 11:37:38 +00:00
unsigned int i;
2014-10-06 12:23:47 +02:00
if (sel) {
txt_curs_sel(text, &linep, &charp);
}
else {
2002-10-12 11:37:38 +00:00
txt_curs_cur(text, &linep, &charp);
}
if (!*linep) {
2002-10-12 11:37:38 +00:00
return;
}
2018-06-17 17:05:51 +02:00
*linep = text->lines.first;
for (i = 0; i < line; i++) {
if ((*linep)->next) {
*linep = (*linep)->next;
}
else {
2002-10-12 11:37:38 +00:00
break;
}
2002-10-12 11:37:38 +00:00
}
if (ch > (unsigned int)((*linep)->len)) {
ch = (unsigned int)((*linep)->len);
}
*charp = ch;
2018-06-17 17:05:51 +02:00
if (!sel) {
txt_pop_sel(text);
}
2002-10-12 11:37:38 +00:00
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Text Selection Functions
* \{ */
2002-10-12 11:37:38 +00:00
static void txt_curs_swap(Text *text)
2002-10-12 11:37:38 +00:00
{
TextLine *tmpl;
int tmpc;
2018-06-17 17:05:51 +02:00
tmpl = text->curl;
text->curl = text->sell;
text->sell = tmpl;
tmpc = text->curc;
text->curc = text->selc;
text->selc = tmpc;
2002-10-12 11:37:38 +00:00
}
static void txt_pop_first(Text *text)
2002-10-12 11:37:38 +00:00
{
if (txt_get_span(text->curl, text->sell) < 0 ||
(text->curl == text->sell && text->curc > text->selc)) {
2002-10-12 11:37:38 +00:00
txt_curs_swap(text);
}
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
txt_pop_sel(text);
}
static void txt_pop_last(Text *text)
2002-10-12 11:37:38 +00:00
{
if (txt_get_span(text->curl, text->sell) > 0 ||
(text->curl == text->sell && text->curc < text->selc)) {
2002-10-12 11:37:38 +00:00
txt_curs_swap(text);
}
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
txt_pop_sel(text);
}
void txt_pop_sel(Text *text)
2002-10-12 11:37:38 +00:00
{
text->sell = text->curl;
text->selc = text->curc;
2002-10-12 11:37:38 +00:00
}
void txt_order_cursors(Text *text, const bool reverse)
2002-10-12 11:37:38 +00:00
{
if (!text->curl) {
2002-10-12 11:37:38 +00:00
return;
}
if (!text->sell) {
return;
}
/* Flip so text->curl is before/after text->sell */
if (reverse == false) {
if ((txt_get_span(text->curl, text->sell) < 0) ||
(text->curl == text->sell && text->curc > text->selc)) {
txt_curs_swap(text);
}
}
else {
if ((txt_get_span(text->curl, text->sell) > 0) ||
(text->curl == text->sell && text->curc < text->selc)) {
txt_curs_swap(text);
}
}
2002-10-12 11:37:38 +00:00
}
2014-02-03 18:55:59 +11:00
bool txt_has_sel(Text *text)
2002-10-12 11:37:38 +00:00
{
return ((text->curl != text->sell) || (text->curc != text->selc));
2002-10-12 11:37:38 +00:00
}
static void txt_delete_sel(Text *text)
2002-10-12 11:37:38 +00:00
{
TextLine *tmpl;
char *buf;
2014-10-06 12:23:47 +02:00
if (!text->curl) {
2002-10-12 11:37:38 +00:00
return;
}
if (!text->sell) {
2002-10-12 11:37:38 +00:00
return;
}
2002-10-12 11:37:38 +00:00
if (!txt_has_sel(text)) {
2002-10-12 11:37:38 +00:00
return;
}
2018-06-17 17:05:51 +02:00
txt_order_cursors(text, false);
2002-10-12 11:37:38 +00:00
buf = MEM_mallocN(text->curc + (text->sell->len - text->selc) + 1, "textline_string");
2002-10-12 11:37:38 +00:00
strncpy(buf, text->curl->line, text->curc);
strcpy(buf + text->curc, text->sell->line + text->selc);
buf[text->curc + (text->sell->len - text->selc)] = 0;
make_new_line(text->curl, buf);
2018-06-17 17:05:51 +02:00
tmpl = text->sell;
2002-10-12 11:37:38 +00:00
while (tmpl != text->curl) {
tmpl = tmpl->prev;
if (!tmpl) {
2002-10-12 11:37:38 +00:00
break;
}
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
txt_delete_line(text, tmpl->next);
}
2018-06-17 17:05:51 +02:00
text->sell = text->curl;
text->selc = text->curc;
2002-10-12 11:37:38 +00:00
}
void txt_sel_all(Text *text)
2002-10-12 11:37:38 +00:00
{
text->curl = text->lines.first;
text->curc = 0;
2018-06-17 17:05:51 +02:00
text->sell = text->lines.last;
text->selc = text->sell->len;
2002-10-12 11:37:38 +00:00
}
/**
* Reverse of #txt_pop_sel
* Clears the selection and ensures the cursor is located
* at the selection (where the cursor is visually while editing).
*/
void txt_sel_clear(Text *text)
{
if (text->sell) {
text->curl = text->sell;
text->curc = text->selc;
}
}
void txt_sel_line(Text *text)
2002-10-12 11:37:38 +00:00
{
if (!text->curl) {
2002-10-12 11:37:38 +00:00
return;
}
2018-06-17 17:05:51 +02:00
text->curc = 0;
text->sell = text->curl;
text->selc = text->sell->len;
2002-10-12 11:37:38 +00:00
}
void txt_sel_set(Text *text, int startl, int startc, int endl, int endc)
{
TextLine *froml, *tol;
int fromllen, tollen;
/* Support negative indices. */
if (startl < 0 || endl < 0) {
int end = BLI_listbase_count(&text->lines) - 1;
if (startl < 0) {
startl = end + startl + 1;
}
if (endl < 0) {
endl = end + endl + 1;
}
}
CLAMP_MIN(startl, 0);
CLAMP_MIN(endl, 0);
froml = BLI_findlink(&text->lines, startl);
if (froml == NULL) {
froml = text->lines.last;
}
if (startl == endl) {
tol = froml;
}
else {
tol = BLI_findlink(&text->lines, endl);
if (tol == NULL) {
tol = text->lines.last;
}
}
fromllen = BLI_strlen_utf8(froml->line);
tollen = BLI_strlen_utf8(tol->line);
/* Support negative indices. */
if (startc < 0) {
startc = fromllen + startc + 1;
}
if (endc < 0) {
endc = tollen + endc + 1;
}
CLAMP(startc, 0, fromllen);
CLAMP(endc, 0, tollen);
text->curl = froml;
text->curc = BLI_str_utf8_offset_from_index(froml->line, startc);
text->sell = tol;
text->selc = BLI_str_utf8_offset_from_index(tol->line, endc);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Buffer Conversion for Undo/Redo
*
* Buffer conversion functions that rely on the buffer already being validated.
*
* The only requirement for these functions is that they're reverse-able,
* the undo logic doesn't inspect their content.
*
* Currently buffers:
* - Always ends with a new-line.
* - Are not null terminated.
* \{ */
/**
* Create a buffer, the only requirement is #txt_from_buf_for_undo can decode it.
*/
char *txt_to_buf_for_undo(Text *text, int *r_buf_len)
{
int buf_len = 0;
LISTBASE_FOREACH (const TextLine *, l, &text->lines) {
buf_len += l->len + 1;
}
char *buf = MEM_mallocN(buf_len, __func__);
char *buf_step = buf;
LISTBASE_FOREACH (const TextLine *, l, &text->lines) {
memcpy(buf_step, l->line, l->len);
buf_step += l->len;
*buf_step++ = '\n';
}
*r_buf_len = buf_len;
return buf;
}
/**
* Decode a buffer from #txt_to_buf_for_undo.
*/
void txt_from_buf_for_undo(Text *text, const char *buf, int buf_len)
{
const char *buf_end = buf + buf_len;
const char *buf_step = buf;
/* First re-use existing lines.
* Good for undo since it means in practice many operations re-use all
* except for the modified line. */
TextLine *l_src = text->lines.first;
BLI_listbase_clear(&text->lines);
while (buf_step != buf_end && l_src) {
/* New lines are ensured by #txt_to_buf_for_undo. */
const char *buf_step_next = strchr(buf_step, '\n');
const int len = buf_step_next - buf_step;
TextLine *l = l_src;
l_src = l_src->next;
if (l->len != len) {
l->line = MEM_reallocN(l->line, len + 1);
l->len = len;
}
MEM_SAFE_FREE(l->format);
memcpy(l->line, buf_step, len);
l->line[len] = '\0';
BLI_addtail(&text->lines, l);
buf_step = buf_step_next + 1;
}
/* If we have extra lines. */
while (l_src != NULL) {
TextLine *l_src_next = l_src->next;
MEM_freeN(l_src->line);
if (l_src->format) {
MEM_freeN(l_src->format);
}
MEM_freeN(l_src);
l_src = l_src_next;
}
while (buf_step != buf_end) {
/* New lines are ensured by #txt_to_buf_for_undo. */
const char *buf_step_next = strchr(buf_step, '\n');
const int len = buf_step_next - buf_step;
TextLine *l = MEM_mallocN(sizeof(TextLine), "textline");
l->line = MEM_mallocN(len + 1, "textline_string");
l->len = len;
l->format = NULL;
memcpy(l->line, buf_step, len);
l->line[len] = '\0';
BLI_addtail(&text->lines, l);
buf_step = buf_step_next + 1;
}
text->curl = text->sell = text->lines.first;
text->curc = text->selc = 0;
txt_make_dirty(text);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cut and Paste Functions
* \{ */
2002-10-12 11:37:38 +00:00
char *txt_to_buf(Text *text, int *r_buf_strlen)
2002-10-12 11:37:38 +00:00
{
int length;
TextLine *tmp, *linef, *linel;
int charf, charl;
char *buf;
2014-10-06 12:23:47 +02:00
if (r_buf_strlen) {
*r_buf_strlen = 0;
}
if (!text->curl) {
2002-10-12 11:37:38 +00:00
return NULL;
}
if (!text->sell) {
2002-10-12 11:37:38 +00:00
return NULL;
}
if (!text->lines.first) {
return NULL;
}
linef = text->lines.first;
charf = 0;
2018-06-17 17:05:51 +02:00
linel = text->lines.last;
charl = linel->len;
2002-10-12 11:37:38 +00:00
if (linef == text->lines.last) {
length = charl - charf;
2002-10-12 11:37:38 +00:00
buf = MEM_mallocN(length + 2, "text buffer");
2018-06-17 17:05:51 +02:00
BLI_strncpy(buf, linef->line + charf, length + 1);
buf[length] = 0;
}
else {
length = linef->len - charf;
length += charl;
length += 2; /* For the 2 '\n' */
2018-06-17 17:05:51 +02:00
tmp = linef->next;
while (tmp && tmp != linel) {
length += tmp->len + 1;
tmp = tmp->next;
2002-10-12 11:37:38 +00:00
}
2018-06-17 17:05:51 +02:00
buf = MEM_mallocN(length + 1, "cut buffer");
2002-10-12 11:37:38 +00:00
strncpy(buf, linef->line + charf, linef->len - charf);
length = linef->len - charf;
2018-06-17 17:05:51 +02:00
buf[length++] = '\n';
2018-06-17 17:05:51 +02:00
tmp = linef->next;
while (tmp && tmp != linel) {
strncpy(buf + length, tmp->line, tmp->len);
length += tmp->len;
2018-06-17 17:05:51 +02:00
buf[length++] = '\n';
2018-06-17 17:05:51 +02:00
tmp = tmp->next;
2002-10-12 11:37:38 +00:00
}
strncpy(buf + length, linel->line, charl);
length += charl;
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
/* python compiler wants an empty end line */
buf[length++] = '\n';
buf[length] = 0;
2002-10-12 11:37:38 +00:00
}
2018-06-17 17:05:51 +02:00
if (r_buf_strlen) {
*r_buf_strlen = length;
}
2002-10-12 11:37:38 +00:00
return buf;
}
char *txt_sel_to_buf(Text *text, int *r_buf_strlen)
2002-10-12 11:37:38 +00:00
{
char *buf;
int length = 0;
2002-10-12 11:37:38 +00:00
TextLine *tmp, *linef, *linel;
int charf, charl;
2014-10-06 12:23:47 +02:00
if (r_buf_strlen) {
*r_buf_strlen = 0;
}
if (!text->curl) {
2002-10-12 11:37:38 +00:00
return NULL;
}
if (!text->sell) {
2002-10-12 11:37:38 +00:00
return NULL;
}
2018-06-17 17:05:51 +02:00
if (text->curl == text->sell) {
linef = linel = text->curl;
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
if (text->curc < text->selc) {
charf = text->curc;
charl = text->selc;
}
else {
charf = text->selc;
charl = text->curc;
2002-10-12 11:37:38 +00:00
}
}
else if (txt_get_span(text->curl, text->sell) < 0) {
linef = text->sell;
linel = text->curl;
2002-10-12 11:37:38 +00:00
charf = text->selc;
charl = text->curc;
}
else {
linef = text->curl;
linel = text->sell;
2018-06-17 17:05:51 +02:00
charf = text->curc;
charl = text->selc;
2002-10-12 11:37:38 +00:00
}
if (linef == linel) {
length = charl - charf;
2002-10-12 11:37:38 +00:00
buf = MEM_mallocN(length + 1, "sel buffer");
2018-06-17 17:05:51 +02:00
BLI_strncpy(buf, linef->line + charf, length + 1);
}
else {
length += linef->len - charf;
length += charl;
2002-10-12 11:37:38 +00:00
length++; /* For the '\n' */
2018-06-17 17:05:51 +02:00
tmp = linef->next;
while (tmp && tmp != linel) {
length += tmp->len + 1;
tmp = tmp->next;
2002-10-12 11:37:38 +00:00
}
2018-06-17 17:05:51 +02:00
buf = MEM_mallocN(length + 1, "sel buffer");
2018-06-17 17:05:51 +02:00
strncpy(buf, linef->line + charf, linef->len - charf);
length = linef->len - charf;
2018-06-17 17:05:51 +02:00
buf[length++] = '\n';
2018-06-17 17:05:51 +02:00
tmp = linef->next;
while (tmp && tmp != linel) {
strncpy(buf + length, tmp->line, tmp->len);
length += tmp->len;
2018-06-17 17:05:51 +02:00
buf[length++] = '\n';
2018-06-17 17:05:51 +02:00
tmp = tmp->next;
2002-10-12 11:37:38 +00:00
}
strncpy(buf + length, linel->line, charl);
length += charl;
2018-06-17 17:05:51 +02:00
buf[length] = 0;
}
2002-10-12 11:37:38 +00:00
if (r_buf_strlen) {
*r_buf_strlen = length;
}
2002-10-12 11:37:38 +00:00
return buf;
}
void txt_insert_buf(Text *text, const char *in_buffer)
2002-10-12 11:37:38 +00:00
{
int l = 0, len;
size_t i = 0, j;
2002-10-12 11:37:38 +00:00
TextLine *add;
char *buffer;
if (!in_buffer) {
2002-10-12 11:37:38 +00:00
return;
}
txt_delete_sel(text);
len = strlen(in_buffer);
buffer = BLI_strdupn(in_buffer, len);
len += txt_extended_ascii_as_utf8(&buffer);
2002-10-12 11:37:38 +00:00
/* Read the first line (or as close as possible */
while (buffer[i] && buffer[i] != '\n') {
txt_add_raw_char(text, BLI_str_utf8_as_unicode_step(buffer, &i));
}
if (buffer[i] == '\n') {
txt_split_curline(text);
i++;
while (i < len) {
l = 0;
while (buffer[i] && buffer[i] != '\n') {
i++;
l++;
}
if (buffer[i] == '\n') {
add = txt_new_linen(buffer + (i - l), l);
BLI_insertlinkbefore(&text->lines, text->curl, add);
i++;
}
else {
for (j = i - l; j < i && j < len;) {
txt_add_raw_char(text, BLI_str_utf8_as_unicode_step(buffer, &j));
}
break;
}
2002-10-12 11:37:38 +00:00
}
}
MEM_freeN(buffer);
2002-10-12 11:37:38 +00:00
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Find String in Text
* \{ */
int txt_find_string(Text *text, const char *findstr, int wrap, int match_case)
{
TextLine *tl, *startl;
const char *s = NULL;
if (!text->curl || !text->sell) {
return 0;
}
txt_order_cursors(text, false);
tl = startl = text->sell;
if (match_case) {
s = strstr(&tl->line[text->selc], findstr);
}
else {
s = BLI_strcasestr(&tl->line[text->selc], findstr);
}
while (!s) {
tl = tl->next;
if (!tl) {
if (wrap) {
tl = text->lines.first;
}
else {
break;
}
}
if (match_case) {
s = strstr(tl->line, findstr);
}
else {
s = BLI_strcasestr(tl->line, findstr);
}
if (tl == startl) {
break;
}
}
if (s) {
int newl = txt_get_span(text->lines.first, tl);
int newc = (int)(s - tl->line);
txt_move_to(text, newl, newc, 0);
txt_move_to(text, newl, newc + strlen(findstr), 1);
return 1;
}
return 0;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Line Editing Functions
* \{ */
2002-10-12 11:37:38 +00:00
void txt_split_curline(Text *text)
{
2002-10-12 11:37:38 +00:00
TextLine *ins;
char *left, *right;
2014-10-06 12:23:47 +02:00
if (!text->curl) {
2002-10-12 11:37:38 +00:00
return;
}
2002-10-12 11:37:38 +00:00
txt_delete_sel(text);
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
/* Make the two half strings */
left = MEM_mallocN(text->curc + 1, "textline_string");
if (text->curc) {
2002-10-12 11:37:38 +00:00
memcpy(left, text->curl->line, text->curc);
}
left[text->curc] = 0;
2018-06-17 17:05:51 +02:00
right = MEM_mallocN(text->curl->len - text->curc + 1, "textline_string");
memcpy(right, text->curl->line + text->curc, text->curl->len - text->curc + 1);
2002-10-12 11:37:38 +00:00
MEM_freeN(text->curl->line);
if (text->curl->format) {
MEM_freeN(text->curl->format);
}
2002-10-12 11:37:38 +00:00
/* Make the new TextLine */
2018-06-17 17:05:51 +02:00
ins = MEM_mallocN(sizeof(TextLine), "textline");
ins->line = left;
ins->format = NULL;
ins->len = text->curc;
text->curl->line = right;
text->curl->format = NULL;
text->curl->len = text->curl->len - text->curc;
2018-06-17 17:05:51 +02:00
BLI_insertlinkbefore(&text->lines, text->curl, ins);
2018-06-17 17:05:51 +02:00
text->curc = 0;
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
txt_make_dirty(text);
txt_clean_text(text);
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
txt_pop_sel(text);
}
static void txt_delete_line(Text *text, TextLine *line)
2002-10-12 11:37:38 +00:00
{
if (!text->curl) {
2002-10-12 11:37:38 +00:00
return;
}
2002-10-12 11:37:38 +00:00
BLI_remlink(&text->lines, line);
2018-06-17 17:05:51 +02:00
if (line->line) {
2002-10-12 11:37:38 +00:00
MEM_freeN(line->line);
}
if (line->format) {
MEM_freeN(line->format);
}
2002-10-12 11:37:38 +00:00
MEM_freeN(line);
txt_make_dirty(text);
txt_clean_text(text);
}
static void txt_combine_lines(Text *text, TextLine *linea, TextLine *lineb)
2002-10-12 11:37:38 +00:00
{
char *tmp, *s;
if (!linea || !lineb) {
return;
}
tmp = MEM_mallocN(linea->len + lineb->len + 1, "textline_string");
2018-06-17 17:05:51 +02:00
s = tmp;
s += BLI_strcpy_rlen(s, linea->line);
s += BLI_strcpy_rlen(s, lineb->line);
(void)s;
make_new_line(linea, tmp);
2018-06-17 17:05:51 +02:00
txt_delete_line(text, lineb);
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
txt_make_dirty(text);
txt_clean_text(text);
}
void txt_duplicate_line(Text *text)
{
TextLine *textline;
2018-06-17 17:05:51 +02:00
if (!text->curl) {
2014-10-06 12:23:47 +02:00
return;
}
2018-06-17 17:05:51 +02:00
if (text->curl == text->sell) {
textline = txt_new_line(text->curl->line);
BLI_insertlinkafter(&text->lines, text->curl, textline);
2018-06-17 17:05:51 +02:00
txt_make_dirty(text);
txt_clean_text(text);
}
}
void txt_delete_char(Text *text)
2002-10-12 11:37:38 +00:00
{
unsigned int c = '\n';
2014-10-06 12:23:47 +02:00
if (!text->curl) {
2002-10-12 11:37:38 +00:00
return;
}
2002-10-12 11:37:38 +00:00
if (txt_has_sel(text)) { /* deleting a selection */
txt_delete_sel(text);
txt_make_dirty(text);
return;
2002-10-12 11:37:38 +00:00
}
if (text->curc == text->curl->len) { /* Appending two lines */
2002-10-12 11:37:38 +00:00
if (text->curl->next) {
txt_combine_lines(text, text->curl, text->curl->next);
txt_pop_sel(text);
}
else {
return;
}
}
else { /* Just deleting a char */
size_t c_len = 0;
c = BLI_str_utf8_as_unicode_and_size(text->curl->line + text->curc, &c_len);
UNUSED_VARS(c);
2018-06-17 17:05:51 +02:00
memmove(text->curl->line + text->curc,
text->curl->line + text->curc + c_len,
text->curl->len - text->curc - c_len + 1);
text->curl->len -= c_len;
2002-10-12 11:37:38 +00:00
txt_pop_sel(text);
}
txt_make_dirty(text);
txt_clean_text(text);
}
void txt_delete_word(Text *text)
{
txt_jump_right(text, true, true);
txt_delete_sel(text);
txt_make_dirty(text);
}
void txt_backspace_char(Text *text)
{
unsigned int c = '\n';
2018-06-17 17:05:51 +02:00
if (!text->curl) {
2002-10-12 11:37:38 +00:00
return;
}
2018-06-17 17:05:51 +02:00
if (txt_has_sel(text)) { /* deleting a selection */
txt_delete_sel(text);
txt_make_dirty(text);
return;
2002-10-12 11:37:38 +00:00
}
if (text->curc == 0) { /* Appending two lines */
if (!text->curl->prev) {
return;
}
2018-06-17 17:05:51 +02:00
text->curl = text->curl->prev;
text->curc = text->curl->len;
2018-06-17 17:05:51 +02:00
txt_combine_lines(text, text->curl, text->curl->next);
txt_pop_sel(text);
}
else { /* Just backspacing a char */
size_t c_len = 0;
const char *prev = BLI_str_prev_char_utf8(text->curl->line + text->curc);
c = BLI_str_utf8_as_unicode_and_size(prev, &c_len);
UNUSED_VARS(c);
2018-06-17 17:05:51 +02:00
/* source and destination overlap, don't use memcpy() */
memmove(text->curl->line + text->curc - c_len,
text->curl->line + text->curc,
text->curl->len - text->curc + 1);
text->curl->len -= c_len;
text->curc -= c_len;
txt_pop_sel(text);
2002-10-12 11:37:38 +00:00
}
txt_make_dirty(text);
txt_clean_text(text);
}
void txt_backspace_word(Text *text)
{
txt_jump_left(text, true, true);
txt_delete_sel(text);
txt_make_dirty(text);
}
/* Max spaces to replace a tab with, currently hardcoded to TXT_TABSIZE = 4.
2012-03-01 12:20:18 +00:00
* Used by txt_convert_tab_to_spaces, indent and unindent.
* Remember to change this string according to max tab size */
static char tab_to_spaces[] = " ";
static void txt_convert_tab_to_spaces(Text *text)
{
/* sb aims to pad adjust the tab-width needed so that the right number of spaces
* is added so that the indention of the line is the right width (i.e. aligned
* to multiples of TXT_TABSIZE)
*/
const char *sb = &tab_to_spaces[text->curc % TXT_TABSIZE];
txt_insert_buf(text, sb);
}
static bool txt_add_char_intern(Text *text, unsigned int add, bool replace_tabs)
{
char *tmp, ch[BLI_UTF8_MAX];
size_t add_len;
2014-10-06 12:23:47 +02:00
if (!text->curl) {
2002-10-12 11:37:38 +00:00
return 0;
}
2002-10-12 11:37:38 +00:00
if (add == '\n') {
txt_split_curline(text);
2014-12-01 17:11:18 +01:00
return true;
2002-10-12 11:37:38 +00:00
}
2018-06-17 17:05:51 +02:00
/* insert spaces rather than tabs */
if (add == '\t' && replace_tabs) {
txt_convert_tab_to_spaces(text);
2014-12-01 17:11:18 +01:00
return true;
}
txt_delete_sel(text);
add_len = BLI_str_utf8_from_unicode(add, ch);
2018-06-17 17:05:51 +02:00
tmp = MEM_mallocN(text->curl->len + add_len + 1, "textline_string");
2018-06-17 17:05:51 +02:00
memcpy(tmp, text->curl->line, text->curc);
memcpy(tmp + text->curc, ch, add_len);
memcpy(
tmp + text->curc + add_len, text->curl->line + text->curc, text->curl->len - text->curc + 1);
make_new_line(text->curl, tmp);
2018-06-17 17:05:51 +02:00
text->curc += add_len;
2002-10-12 11:37:38 +00:00
txt_pop_sel(text);
2018-06-17 17:05:51 +02:00
2002-10-12 11:37:38 +00:00
txt_make_dirty(text);
txt_clean_text(text);
return 1;
}
bool txt_add_char(Text *text, unsigned int add)
{
return txt_add_char_intern(text, add, (text->flags & TXT_TABSTOSPACES) != 0);
}
bool txt_add_raw_char(Text *text, unsigned int add)
{
return txt_add_char_intern(text, add, 0);
}
void txt_delete_selected(Text *text)
2.5: Text Editor back. There was very little structure in this code, using many globals and duplicated code. Now it should be better structured. Most things should work, the main parts that are not back yet are the python plugins and markers. Notes: * Blenfont is used for drawing the text, nicely anti-aliased. * A monospace truetype font was added, since that is needed for the text editor. It's Bitstream Vera Sans Mono. This is the default gnome terminal font, but it doesn't fit entirely well with the other font I think, can be changed easily of course. * Clipboard copy/cut/paste now always uses the system clipboard, the code for the own cut buffer was removed. * The interface buttons should support copy/cut/paste again now as well. * WM_clipboard_text_get/WM_clipboard_text_set were added to the windowmanager code. * Find panel is now a kind of second header, instead of a panel. This needs especially a way to start editing the text field immediately on open still. * Operators are independent of the actual space when possible, was a bit of puzzling but got it solved nice with notifiers, and some lazy init for syntax highlight in the drawing code. * RNA was created for the text editor space and used for buttons. * Operators: * New, Open, Reload, Save, Save As, Make Internal * Run Script, Refresh Pyconstraints * Copy, Cut, Paste * Convert Whitespace, Uncomment, Comment, Indent, Unindent * Line Break, Insert * Next Marker, Previous Marker, Clear All Markers, Mark All * Select Line, Select All * Jump, Move, Move Select, Delete, Toggle Overwrite * Scroll, Scroll Bar, Set Cursor, Line Number * Find and Replace, Find, Replace, Find Set Selected, Replace Set Selected * To 3D Object * Resolve Conflict
2009-02-28 23:33:35 +00:00
{
txt_delete_sel(text);
2.5: Text Editor back. There was very little structure in this code, using many globals and duplicated code. Now it should be better structured. Most things should work, the main parts that are not back yet are the python plugins and markers. Notes: * Blenfont is used for drawing the text, nicely anti-aliased. * A monospace truetype font was added, since that is needed for the text editor. It's Bitstream Vera Sans Mono. This is the default gnome terminal font, but it doesn't fit entirely well with the other font I think, can be changed easily of course. * Clipboard copy/cut/paste now always uses the system clipboard, the code for the own cut buffer was removed. * The interface buttons should support copy/cut/paste again now as well. * WM_clipboard_text_get/WM_clipboard_text_set were added to the windowmanager code. * Find panel is now a kind of second header, instead of a panel. This needs especially a way to start editing the text field immediately on open still. * Operators are independent of the actual space when possible, was a bit of puzzling but got it solved nice with notifiers, and some lazy init for syntax highlight in the drawing code. * RNA was created for the text editor space and used for buttons. * Operators: * New, Open, Reload, Save, Save As, Make Internal * Run Script, Refresh Pyconstraints * Copy, Cut, Paste * Convert Whitespace, Uncomment, Comment, Indent, Unindent * Line Break, Insert * Next Marker, Previous Marker, Clear All Markers, Mark All * Select Line, Select All * Jump, Move, Move Select, Delete, Toggle Overwrite * Scroll, Scroll Bar, Set Cursor, Line Number * Find and Replace, Find, Replace, Find Set Selected, Replace Set Selected * To 3D Object * Resolve Conflict
2009-02-28 23:33:35 +00:00
txt_make_dirty(text);
}
bool txt_replace_char(Text *text, unsigned int add)
{
unsigned int del;
size_t del_size = 0, add_size;
char ch[BLI_UTF8_MAX];
if (!text->curl) {
2014-12-01 17:11:18 +01:00
return false;
}
/* If text is selected or we're at the end of the line just use txt_add_char */
if (text->curc == text->curl->len || txt_has_sel(text) || add == '\n') {
return txt_add_char(text, add);
}
del = BLI_str_utf8_as_unicode_and_size(text->curl->line + text->curc, &del_size);
UNUSED_VARS(del);
add_size = BLI_str_utf8_from_unicode(add, ch);
if (add_size > del_size) {
char *tmp = MEM_mallocN(text->curl->len + add_size - del_size + 1, "textline_string");
memcpy(tmp, text->curl->line, text->curc);
memcpy(tmp + text->curc + add_size,
text->curl->line + text->curc + del_size,
text->curl->len - text->curc - del_size + 1);
MEM_freeN(text->curl->line);
text->curl->line = tmp;
}
else if (add_size < del_size) {
char *tmp = text->curl->line;
memmove(tmp + text->curc + add_size,
tmp + text->curc + del_size,
text->curl->len - text->curc - del_size + 1);
}
memcpy(text->curl->line + text->curc, ch, add_size);
text->curc += add_size;
text->curl->len += add_size - del_size;
txt_pop_sel(text);
txt_make_dirty(text);
txt_clean_text(text);
2014-12-01 17:11:18 +01:00
return true;
}
/**
* Generic prefix operation, use for comment & indent.
*
* \note caller must handle undo.
*/
static void txt_select_prefix(Text *text, const char *add, bool skip_blank_lines)
{
int len, num, curc_old, selc_old;
char *tmp;
const int indentlen = strlen(add);
BLI_assert(!ELEM(NULL, text->curl, text->sell));
curc_old = text->curc;
selc_old = text->selc;
num = 0;
while (true) {
/* don't indent blank lines */
if ((text->curl->len != 0) || (skip_blank_lines == 0)) {
tmp = MEM_mallocN(text->curl->len + indentlen + 1, "textline_string");
text->curc = 0;
if (text->curc) {
memcpy(tmp, text->curl->line, text->curc); /* XXX never true, check prev line */
}
memcpy(tmp + text->curc, add, indentlen);
len = text->curl->len - text->curc;
if (len > 0) {
memcpy(tmp + text->curc + indentlen, text->curl->line + text->curc, len);
}
tmp[text->curl->len + indentlen] = 0;
make_new_line(text->curl, tmp);
text->curc += indentlen;
txt_make_dirty(text);
txt_clean_text(text);
}
2018-06-17 17:05:51 +02:00
if (text->curl == text->sell) {
if (text->curl->len != 0) {
text->selc += indentlen;
}
break;
}
text->curl = text->curl->next;
num++;
}
while (num > 0) {
text->curl = text->curl->prev;
num--;
}
2018-06-17 17:05:51 +02:00
/* Keep the cursor left aligned if we don't have a selection. */
if (curc_old == 0 && !(text->curl == text->sell && curc_old == selc_old)) {
if (text->curl == text->sell) {
if (text->curc == text->selc) {
text->selc = 0;
}
}
text->curc = 0;
}
else {
if (text->curl->len != 0) {
text->curc = curc_old + indentlen;
}
}
}
/**
* Generic un-prefix operation, use for comment & indent.
*
* \param require_all: When true, all non-empty lines must have this prefix.
* Needed for comments where we might want to un-comment a block which contains some comments.
*
* \note caller must handle undo.
*/
static bool txt_select_unprefix(Text *text, const char *remove, const bool require_all)
{
int num = 0;
const int indentlen = strlen(remove);
bool unindented_first = false;
2019-08-01 20:31:57 +10:00
bool changed_any = false;
BLI_assert(!ELEM(NULL, text->curl, text->sell));
if (require_all) {
/* Check all non-empty lines use this 'remove',
* so the operation is applied equally or not at all. */
TextLine *l = text->curl;
while (true) {
if (STREQLEN(l->line, remove, indentlen)) {
/* pass */
}
else {
/* Blank lines or whitespace can be skipped. */
for (int i = 0; i < l->len; i++) {
if (!ELEM(l->line[i], '\t', ' ')) {
return false;
}
}
}
if (l == text->sell) {
break;
}
l = l->next;
}
}
while (true) {
bool changed = false;
if (STREQLEN(text->curl->line, remove, indentlen)) {
if (num == 0) {
unindented_first = true;
}
text->curl->len -= indentlen;
memmove(text->curl->line, text->curl->line + indentlen, text->curl->len + 1);
changed = true;
2019-08-01 20:31:57 +10:00
changed_any = true;
}
txt_make_dirty(text);
txt_clean_text(text);
if (text->curl == text->sell) {
if (changed) {
text->selc = MAX2(text->selc - indentlen, 0);
}
break;
}
text->curl = text->curl->next;
num++;
}
if (unindented_first) {
text->curc = MAX2(text->curc - indentlen, 0);
}
while (num > 0) {
text->curl = text->curl->prev;
num--;
}
/* caller must handle undo */
2019-08-01 20:31:57 +10:00
return changed_any;
}
void txt_comment(Text *text)
{
const char *prefix = "#";
2014-10-06 12:23:47 +02:00
if (ELEM(NULL, text->curl, text->sell)) {
return;
}
const bool skip_blank_lines = txt_has_sel(text);
txt_select_prefix(text, prefix, skip_blank_lines);
}
2019-08-01 20:31:57 +10:00
bool txt_uncomment(Text *text)
{
const char *prefix = "#";
2014-10-06 12:23:47 +02:00
if (ELEM(NULL, text->curl, text->sell)) {
2019-08-01 20:31:57 +10:00
return false;
}
return txt_select_unprefix(text, prefix, true);
}
void txt_indent(Text *text)
{
const char *prefix = (text->flags & TXT_TABSTOSPACES) ? tab_to_spaces : "\t";
if (ELEM(NULL, text->curl, text->sell)) {
return;
}
txt_select_prefix(text, prefix, true);
}
2019-08-01 20:31:57 +10:00
bool txt_unindent(Text *text)
{
const char *prefix = (text->flags & TXT_TABSTOSPACES) ? tab_to_spaces : "\t";
2018-06-17 17:05:51 +02:00
if (ELEM(NULL, text->curl, text->sell)) {
2019-08-01 20:31:57 +10:00
return false;
}
return txt_select_unprefix(text, prefix, false);
}
void txt_move_lines(struct Text *text, const int direction)
{
TextLine *line_other;
BLI_assert(ELEM(direction, TXT_MOVE_LINE_UP, TXT_MOVE_LINE_DOWN));
if (!text->curl || !text->sell) {
2014-10-06 12:23:47 +02:00
return;
}
2018-06-17 17:05:51 +02:00
txt_order_cursors(text, false);
line_other = (direction == TXT_MOVE_LINE_DOWN) ? text->sell->next : text->curl->prev;
2018-06-17 17:05:51 +02:00
if (!line_other) {
return;
}
2018-06-17 17:05:51 +02:00
BLI_remlink(&text->lines, line_other);
if (direction == TXT_MOVE_LINE_DOWN) {
BLI_insertlinkbefore(&text->lines, text->curl, line_other);
}
else {
BLI_insertlinkafter(&text->lines, text->sell, line_other);
}
txt_make_dirty(text);
txt_clean_text(text);
}
2013-03-14 05:52:30 +00:00
int txt_setcurr_tab_spaces(Text *text, int space)
{
int i = 0;
int test = 0;
const char *word = ":";
const char *comm = "#";
const char indent = (text->flags & TXT_TABSTOSPACES) ? ' ' : '\t';
static const char *back_words[] = {"return", "break", "continue", "pass", "yield", NULL};
if (!text->curl) {
return 0;
}
while (text->curl->line[i] == indent) {
// we only count those tabs/spaces that are before any text or before the curs;
if (i == text->curc) {
return i;
}
i++;
}
if (strstr(text->curl->line, word)) {
/* if we find a ':' on this line, then add a tab but not if it is:
2018-11-14 12:53:15 +11:00
* 1) in a comment
* 2) within an identifier
* 3) after the cursor (text->curc), i.e. when creating space before a function def T25414.
*/
2014-04-11 11:25:41 +10:00
int a;
bool is_indent = false;
for (a = 0; (a < text->curc) && (text->curl->line[a] != '\0'); a++) {
char ch = text->curl->line[a];
if (ch == '#') {
break;
}
if (ch == ':') {
is_indent = 1;
}
2020-11-06 12:30:59 +11:00
else if (!ELEM(ch, ' ', '\t')) {
is_indent = 0;
}
}
if (is_indent) {
i += space;
}
}
for (test = 0; back_words[test]; test++) {
/* if there are these key words then remove a tab because we are done with the block */
if (strstr(text->curl->line, back_words[test]) && i > 0) {
if (strcspn(text->curl->line, back_words[test]) < strcspn(text->curl->line, comm)) {
i -= space;
}
}
}
return i;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Character Queries
* \{ */
int text_check_bracket(const char ch)
{
int a;
char opens[] = "([{";
char close[] = ")]}";
for (a = 0; a < (sizeof(opens) - 1); a++) {
if (ch == opens[a]) {
return a + 1;
}
if (ch == close[a]) {
return -(a + 1);
}
}
return 0;
}
/* TODO, have a function for operators -
* http://docs.python.org/py3k/reference/lexical_analysis.html#operators */
2014-02-03 18:55:59 +11:00
bool text_check_delim(const char ch)
{
int a;
char delims[] = "():\"\' ~!%^&*-+=[]{};/<>|.#\t,@";
for (a = 0; a < (sizeof(delims) - 1); a++) {
if (ch == delims[a]) {
2014-12-01 17:11:18 +01:00
return true;
}
}
2014-12-01 17:11:18 +01:00
return false;
}
2014-02-03 18:55:59 +11:00
bool text_check_digit(const char ch)
{
if (ch < '0') {
2014-12-01 17:11:18 +01:00
return false;
}
if (ch <= '9') {
2014-12-01 17:11:18 +01:00
return true;
}
2014-12-01 17:11:18 +01:00
return false;
}
2014-02-03 18:55:59 +11:00
bool text_check_identifier(const char ch)
{
if (ch < '0') {
2014-12-01 17:11:18 +01:00
return false;
}
if (ch <= '9') {
2014-12-01 17:11:18 +01:00
return true;
}
if (ch < 'A') {
2014-12-01 17:11:18 +01:00
return false;
}
if (ch <= 'Z' || ch == '_') {
2014-12-01 17:11:18 +01:00
return true;
}
if (ch < 'a') {
2014-12-01 17:11:18 +01:00
return false;
}
if (ch <= 'z') {
2014-12-01 17:11:18 +01:00
return true;
}
2014-12-01 17:11:18 +01:00
return false;
}
2014-02-03 18:55:59 +11:00
bool text_check_identifier_nodigit(const char ch)
{
if (ch <= '9') {
2014-12-01 17:11:18 +01:00
return false;
}
if (ch < 'A') {
2014-12-01 17:11:18 +01:00
return false;
}
if (ch <= 'Z' || ch == '_') {
2014-12-01 17:11:18 +01:00
return true;
}
if (ch < 'a') {
2014-12-01 17:11:18 +01:00
return false;
}
if (ch <= 'z') {
2014-12-01 17:11:18 +01:00
return true;
}
2014-12-01 17:11:18 +01:00
return false;
}
#ifndef WITH_PYTHON
int text_check_identifier_unicode(const unsigned int ch)
{
return (ch < 255 && text_check_identifier((unsigned int)ch));
}
int text_check_identifier_nodigit_unicode(const unsigned int ch)
{
return (ch < 255 && text_check_identifier_nodigit((char)ch));
}
#endif /* WITH_PYTHON */
2014-02-03 18:55:59 +11:00
bool text_check_whitespace(const char ch)
{
2020-11-06 12:51:49 +11:00
if (ELEM(ch, ' ', '\t', '\r', '\n')) {
2014-12-01 17:11:18 +01:00
return true;
}
2014-12-01 17:11:18 +01:00
return false;
}
int text_find_identifier_start(const char *str, int i)
{
2012-12-31 17:19:55 +00:00
if (UNLIKELY(i <= 0)) {
return 0;
}
while (i--) {
if (!text_check_identifier(str[i])) {
break;
}
}
i++;
return i;
}
/** \} */