The most common use of the text editor seems to be for scripting. Having
line numbers and syntax highlighting enabled by default seems sensible.
Syntax highlighting is now enabled by default, but is automatically
disabled when the datablock has a non-highlighted extension.
Highlighting is enabled for filenames like:
- Text
- Text.001
- somefile.py
and is automatically disabled when the datablock has an extension for
which Blender has no syntax highlighter registered.
Reviewers: billreynish, campbellbarton
Subscribers: brecht, billreynish
Differential Revision: https://developer.blender.org/D5472
1851 lines
48 KiB
C
1851 lines
48 KiB
C
/*
|
|
* 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* 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,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup sptext
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLF_api.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_math.h"
|
|
|
|
#include "DNA_text_types.h"
|
|
#include "DNA_space_types.h"
|
|
#include "DNA_screen_types.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_suggestions.h"
|
|
#include "BKE_text.h"
|
|
#include "BKE_screen.h"
|
|
|
|
#include "ED_text.h"
|
|
|
|
#include "GPU_immediate.h"
|
|
#include "GPU_state.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_resources.h"
|
|
#include "UI_view2d.h"
|
|
|
|
#include "text_intern.h"
|
|
#include "text_format.h"
|
|
|
|
/******************** text font drawing ******************/
|
|
|
|
typedef struct TextDrawContext {
|
|
int font_id;
|
|
int cwidth;
|
|
int lheight_dpi;
|
|
bool syntax_highlight;
|
|
} TextDrawContext;
|
|
|
|
static void text_draw_context_init(const SpaceText *st, TextDrawContext *tdc)
|
|
{
|
|
tdc->font_id = blf_mono_font;
|
|
tdc->cwidth = 0;
|
|
tdc->lheight_dpi = st->lheight_dpi;
|
|
tdc->syntax_highlight = st->showsyntax && ED_text_is_syntax_highlight_supported(st->text);
|
|
}
|
|
|
|
static void text_font_begin(const TextDrawContext *tdc)
|
|
{
|
|
BLF_size(tdc->font_id, tdc->lheight_dpi, 72);
|
|
}
|
|
|
|
static void text_font_end(const TextDrawContext *UNUSED(tdc))
|
|
{
|
|
}
|
|
|
|
static int text_font_draw(const TextDrawContext *tdc, int x, int y, const char *str)
|
|
{
|
|
int columns;
|
|
|
|
BLF_position(tdc->font_id, x, y, 0);
|
|
columns = BLF_draw_mono(tdc->font_id, str, BLF_DRAW_STR_DUMMY_MAX, tdc->cwidth);
|
|
|
|
return tdc->cwidth * columns;
|
|
}
|
|
|
|
static int text_font_draw_character(const TextDrawContext *tdc, int x, int y, char c)
|
|
{
|
|
BLF_position(tdc->font_id, x, y, 0);
|
|
BLF_draw(tdc->font_id, &c, 1);
|
|
|
|
return tdc->cwidth;
|
|
}
|
|
|
|
static int text_font_draw_character_utf8(const TextDrawContext *tdc, int x, int y, const char *c)
|
|
{
|
|
int columns;
|
|
|
|
const size_t len = BLI_str_utf8_size_safe(c);
|
|
BLF_position(tdc->font_id, x, y, 0);
|
|
columns = BLF_draw_mono(tdc->font_id, c, len, tdc->cwidth);
|
|
|
|
return tdc->cwidth * columns;
|
|
}
|
|
|
|
#if 0
|
|
/* Formats every line of the current text */
|
|
static void txt_format_text(SpaceText *st)
|
|
{
|
|
TextLine *linep;
|
|
|
|
if (!st->text) {
|
|
return;
|
|
}
|
|
|
|
for (linep = st->text->lines.first; linep; linep = linep->next) {
|
|
txt_format_line(st, linep, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Sets the current drawing color based on the format character specified */
|
|
static void format_draw_color(const TextDrawContext *tdc, char formatchar)
|
|
{
|
|
switch (formatchar) {
|
|
case FMT_TYPE_WHITESPACE:
|
|
break;
|
|
case FMT_TYPE_SYMBOL:
|
|
UI_FontThemeColor(tdc->font_id, TH_SYNTAX_S);
|
|
break;
|
|
case FMT_TYPE_COMMENT:
|
|
UI_FontThemeColor(tdc->font_id, TH_SYNTAX_C);
|
|
break;
|
|
case FMT_TYPE_NUMERAL:
|
|
UI_FontThemeColor(tdc->font_id, TH_SYNTAX_N);
|
|
break;
|
|
case FMT_TYPE_STRING:
|
|
UI_FontThemeColor(tdc->font_id, TH_SYNTAX_L);
|
|
break;
|
|
case FMT_TYPE_DIRECTIVE:
|
|
UI_FontThemeColor(tdc->font_id, TH_SYNTAX_D);
|
|
break;
|
|
case FMT_TYPE_SPECIAL:
|
|
UI_FontThemeColor(tdc->font_id, TH_SYNTAX_V);
|
|
break;
|
|
case FMT_TYPE_RESERVED:
|
|
UI_FontThemeColor(tdc->font_id, TH_SYNTAX_R);
|
|
break;
|
|
case FMT_TYPE_KEYWORD:
|
|
UI_FontThemeColor(tdc->font_id, TH_SYNTAX_B);
|
|
break;
|
|
case FMT_TYPE_DEFAULT:
|
|
default:
|
|
UI_FontThemeColor(tdc->font_id, TH_TEXT);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/************************** draw text *****************************/
|
|
|
|
/**
|
|
* Notes on word-wrap
|
|
* --
|
|
* All word-wrap functions follow the algorithm below to maintain consistency:
|
|
* - line:
|
|
* The line to wrap (tabs converted to spaces)
|
|
* - view_width:
|
|
* The maximum number of characters displayable in the region
|
|
* This equals region_width/font_width for the region
|
|
* - wrap_chars:
|
|
* Characters that allow wrapping. This equals [' ', '\t', '-']
|
|
*
|
|
* \code{.py}
|
|
* def wrap(line, view_width, wrap_chars):
|
|
* draw_start = 0
|
|
* draw_end = view_width
|
|
* pos = 0
|
|
* for c in line:
|
|
* if pos-draw_start >= view_width:
|
|
* print line[draw_start:draw_end]
|
|
* draw_start = draw_end
|
|
* draw_end += view_width
|
|
* elif c in wrap_chars:
|
|
* draw_end = pos+1
|
|
* pos += 1
|
|
* print line[draw_start:]
|
|
* \encode
|
|
*/
|
|
|
|
int wrap_width(const SpaceText *st, ARegion *ar)
|
|
{
|
|
int winx = ar->winx - TXT_SCROLL_WIDTH;
|
|
int x, max;
|
|
|
|
x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
|
|
max = st->cwidth ? (winx - x) / st->cwidth : 0;
|
|
return max > 8 ? max : 8;
|
|
}
|
|
|
|
/* Sets (offl, offc) for transforming (line, curs) to its wrapped position */
|
|
void wrap_offset(
|
|
const SpaceText *st, ARegion *ar, TextLine *linein, int cursin, int *offl, int *offc)
|
|
{
|
|
Text *text;
|
|
TextLine *linep;
|
|
int i, j, start, end, max, chop;
|
|
char ch;
|
|
|
|
*offl = *offc = 0;
|
|
|
|
if (!st->text) {
|
|
return;
|
|
}
|
|
if (!st->wordwrap) {
|
|
return;
|
|
}
|
|
|
|
text = st->text;
|
|
|
|
/* Move pointer to first visible line (top) */
|
|
linep = text->lines.first;
|
|
i = st->top;
|
|
while (i > 0 && linep) {
|
|
int lines = text_get_visible_lines(st, ar, linep->line);
|
|
|
|
/* Line before top */
|
|
if (linep == linein) {
|
|
if (lines <= i) {
|
|
/* no visible part of line */
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (i - lines < 0) {
|
|
break;
|
|
}
|
|
else {
|
|
linep = linep->next;
|
|
(*offl) += lines - 1;
|
|
i -= lines;
|
|
}
|
|
}
|
|
|
|
max = wrap_width(st, ar);
|
|
cursin = BLI_str_utf8_offset_to_column(linein->line, cursin);
|
|
|
|
while (linep) {
|
|
start = 0;
|
|
end = max;
|
|
chop = 1;
|
|
*offc = 0;
|
|
for (i = 0, j = 0; linep->line[j]; j += BLI_str_utf8_size_safe(linep->line + j)) {
|
|
int chars;
|
|
int columns = BLI_str_utf8_char_width_safe(linep->line + j); /* = 1 for tab */
|
|
|
|
/* Mimic replacement of tabs */
|
|
ch = linep->line[j];
|
|
if (ch == '\t') {
|
|
chars = st->tabnumber - i % st->tabnumber;
|
|
if (linep == linein && i < cursin) {
|
|
cursin += chars - 1;
|
|
}
|
|
ch = ' ';
|
|
}
|
|
else {
|
|
chars = 1;
|
|
}
|
|
|
|
while (chars--) {
|
|
if (i + columns - start > max) {
|
|
end = MIN2(end, i);
|
|
|
|
if (chop && linep == linein && i >= cursin) {
|
|
if (i == cursin) {
|
|
(*offl)++;
|
|
*offc -= end - start;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
(*offl)++;
|
|
*offc -= end - start;
|
|
|
|
start = end;
|
|
end += max;
|
|
chop = 1;
|
|
}
|
|
else if (ch == ' ' || ch == '-') {
|
|
end = i + 1;
|
|
chop = 0;
|
|
if (linep == linein && i >= cursin) {
|
|
return;
|
|
}
|
|
}
|
|
i += columns;
|
|
}
|
|
}
|
|
if (linep == linein) {
|
|
break;
|
|
}
|
|
linep = linep->next;
|
|
}
|
|
}
|
|
|
|
/* cursin - mem, offc - view */
|
|
void wrap_offset_in_line(
|
|
const SpaceText *st, ARegion *ar, TextLine *linein, int cursin, int *offl, int *offc)
|
|
{
|
|
int i, j, start, end, chars, max, chop;
|
|
char ch;
|
|
|
|
*offl = *offc = 0;
|
|
|
|
if (!st->text) {
|
|
return;
|
|
}
|
|
if (!st->wordwrap) {
|
|
return;
|
|
}
|
|
|
|
max = wrap_width(st, ar);
|
|
|
|
start = 0;
|
|
end = max;
|
|
chop = 1;
|
|
*offc = 0;
|
|
cursin = BLI_str_utf8_offset_to_column(linein->line, cursin);
|
|
|
|
for (i = 0, j = 0; linein->line[j]; j += BLI_str_utf8_size_safe(linein->line + j)) {
|
|
int columns = BLI_str_utf8_char_width_safe(linein->line + j); /* = 1 for tab */
|
|
|
|
/* Mimic replacement of tabs */
|
|
ch = linein->line[j];
|
|
if (ch == '\t') {
|
|
chars = st->tabnumber - i % st->tabnumber;
|
|
if (i < cursin) {
|
|
cursin += chars - 1;
|
|
}
|
|
ch = ' ';
|
|
}
|
|
else {
|
|
chars = 1;
|
|
}
|
|
|
|
while (chars--) {
|
|
if (i + columns - start > max) {
|
|
end = MIN2(end, i);
|
|
|
|
if (chop && i >= cursin) {
|
|
if (i == cursin) {
|
|
(*offl)++;
|
|
*offc -= end - start;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
(*offl)++;
|
|
*offc -= end - start;
|
|
|
|
start = end;
|
|
end += max;
|
|
chop = 1;
|
|
}
|
|
else if (ch == ' ' || ch == '-') {
|
|
end = i + 1;
|
|
chop = 0;
|
|
if (i >= cursin) {
|
|
return;
|
|
}
|
|
}
|
|
i += columns;
|
|
}
|
|
}
|
|
}
|
|
|
|
int text_get_char_pos(const SpaceText *st, const char *line, int cur)
|
|
{
|
|
int a = 0, i;
|
|
|
|
for (i = 0; i < cur && line[i]; i += BLI_str_utf8_size_safe(line + i)) {
|
|
if (line[i] == '\t') {
|
|
a += st->tabnumber - a % st->tabnumber;
|
|
}
|
|
else {
|
|
a += BLI_str_utf8_char_width_safe(line + i);
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
|
|
static const char *txt_utf8_forward_columns(const char *str, int columns, int *padding)
|
|
{
|
|
int col;
|
|
const char *p = str;
|
|
while (*p) {
|
|
col = BLI_str_utf8_char_width(p);
|
|
if (columns - col < 0) {
|
|
break;
|
|
}
|
|
columns -= col;
|
|
p += BLI_str_utf8_size_safe(p);
|
|
if (columns == 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (padding) {
|
|
*padding = *p ? columns : 0;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static int text_draw_wrapped(const SpaceText *st,
|
|
const TextDrawContext *tdc,
|
|
const char *str,
|
|
int x,
|
|
int y,
|
|
int w,
|
|
const char *format,
|
|
int skip)
|
|
{
|
|
const bool use_syntax = (tdc->syntax_highlight && format);
|
|
FlattenString fs;
|
|
int basex, lines;
|
|
int i, wrap, end, max, columns, padding; /* column */
|
|
/* warning, only valid when 'use_syntax' is set */
|
|
int a, fstart, fpos; /* utf8 chars */
|
|
int mi, ma, mstart, mend; /* mem */
|
|
char fmt_prev = 0xff;
|
|
/* don't draw lines below this */
|
|
const int clip_min_y = -(int)(st->lheight_dpi - 1);
|
|
|
|
flatten_string(st, &fs, str);
|
|
str = fs.buf;
|
|
max = w / st->cwidth;
|
|
if (max < 8) {
|
|
max = 8;
|
|
}
|
|
basex = x;
|
|
lines = 1;
|
|
|
|
fpos = fstart = 0;
|
|
mstart = 0;
|
|
mend = txt_utf8_forward_columns(str, max, &padding) - str;
|
|
end = wrap = max - padding;
|
|
|
|
for (i = 0, mi = 0; str[mi]; i += columns, mi += BLI_str_utf8_size_safe(str + mi)) {
|
|
columns = BLI_str_utf8_char_width_safe(str + mi);
|
|
if (i + columns > end) {
|
|
/* skip hidden part of line */
|
|
if (skip) {
|
|
skip--;
|
|
if (use_syntax) {
|
|
/* currently fpos only used when formatting */
|
|
fpos += BLI_strnlen_utf8(str + mstart, mend - mstart);
|
|
}
|
|
fstart = fpos;
|
|
mstart = mend;
|
|
mend = txt_utf8_forward_columns(str + mend, max, &padding) - str;
|
|
end = (wrap += max - padding);
|
|
continue;
|
|
}
|
|
|
|
/* Draw the visible portion of text on the overshot line */
|
|
for (a = fstart, ma = mstart; ma < mend; a++, ma += BLI_str_utf8_size_safe(str + ma)) {
|
|
if (use_syntax) {
|
|
if (fmt_prev != format[a]) {
|
|
format_draw_color(tdc, fmt_prev = format[a]);
|
|
}
|
|
}
|
|
x += text_font_draw_character_utf8(tdc, x, y, str + ma);
|
|
fpos++;
|
|
}
|
|
y -= st->lheight_dpi + TXT_LINE_SPACING;
|
|
x = basex;
|
|
lines++;
|
|
fstart = fpos;
|
|
mstart = mend;
|
|
mend = txt_utf8_forward_columns(str + mend, max, &padding) - str;
|
|
end = (wrap += max - padding);
|
|
|
|
if (y <= clip_min_y) {
|
|
break;
|
|
}
|
|
}
|
|
else if (str[mi] == ' ' || str[mi] == '-') {
|
|
wrap = i + 1;
|
|
mend = mi + 1;
|
|
}
|
|
}
|
|
|
|
/* Draw the remaining text */
|
|
for (a = fstart, ma = mstart; str[ma] && y > clip_min_y;
|
|
a++, ma += BLI_str_utf8_size_safe(str + ma)) {
|
|
if (use_syntax) {
|
|
if (fmt_prev != format[a]) {
|
|
format_draw_color(tdc, fmt_prev = format[a]);
|
|
}
|
|
}
|
|
|
|
x += text_font_draw_character_utf8(tdc, x, y, str + ma);
|
|
}
|
|
|
|
flatten_string_free(&fs);
|
|
|
|
return lines;
|
|
}
|
|
|
|
static void text_draw(const SpaceText *st,
|
|
const TextDrawContext *tdc,
|
|
char *str,
|
|
int cshift,
|
|
int maxwidth,
|
|
int x,
|
|
int y,
|
|
const char *format)
|
|
{
|
|
const bool use_syntax = (tdc->syntax_highlight && format);
|
|
FlattenString fs;
|
|
int columns, size, n, w = 0, padding, amount = 0;
|
|
const char *in = NULL;
|
|
|
|
for (n = flatten_string(st, &fs, str), str = fs.buf; n > 0; n--) {
|
|
columns = BLI_str_utf8_char_width_safe(str);
|
|
size = BLI_str_utf8_size_safe(str);
|
|
|
|
if (!in) {
|
|
if (w >= cshift) {
|
|
padding = w - cshift;
|
|
in = str;
|
|
}
|
|
else if (format) {
|
|
format++;
|
|
}
|
|
}
|
|
if (in) {
|
|
if (maxwidth && w + columns > cshift + maxwidth) {
|
|
break;
|
|
}
|
|
amount++;
|
|
}
|
|
|
|
w += columns;
|
|
str += size;
|
|
}
|
|
if (!in) {
|
|
flatten_string_free(&fs);
|
|
return; /* String is shorter than shift or ends with a padding */
|
|
}
|
|
|
|
x += tdc->cwidth * padding;
|
|
|
|
if (use_syntax) {
|
|
int a, str_shift = 0;
|
|
char fmt_prev = 0xff;
|
|
|
|
for (a = 0; a < amount; a++) {
|
|
if (format[a] != fmt_prev) {
|
|
format_draw_color(tdc, fmt_prev = format[a]);
|
|
}
|
|
x += text_font_draw_character_utf8(tdc, x, y, in + str_shift);
|
|
str_shift += BLI_str_utf8_size_safe(in + str_shift);
|
|
}
|
|
}
|
|
else {
|
|
text_font_draw(tdc, x, y, in);
|
|
}
|
|
|
|
flatten_string_free(&fs);
|
|
}
|
|
|
|
/************************ cache utilities *****************************/
|
|
|
|
typedef struct DrawCache {
|
|
int *line_height;
|
|
int total_lines, nlines;
|
|
|
|
/* this is needed to check cache relevance */
|
|
int winx, wordwrap, showlinenrs, tabnumber;
|
|
short lheight;
|
|
char cwidth;
|
|
char text_id[MAX_ID_NAME];
|
|
|
|
/* for partial lines recalculation */
|
|
short update_flag;
|
|
int valid_head, valid_tail; /* amount of unchanged lines */
|
|
} DrawCache;
|
|
|
|
static void text_drawcache_init(SpaceText *st)
|
|
{
|
|
DrawCache *drawcache = MEM_callocN(sizeof(DrawCache), "text draw cache");
|
|
|
|
drawcache->winx = -1;
|
|
drawcache->nlines = BLI_listbase_count(&st->text->lines);
|
|
drawcache->text_id[0] = '\0';
|
|
|
|
st->drawcache = drawcache;
|
|
}
|
|
|
|
static void text_update_drawcache(SpaceText *st, ARegion *ar)
|
|
{
|
|
DrawCache *drawcache;
|
|
int full_update = 0, nlines = 0;
|
|
Text *txt = st->text;
|
|
|
|
if (!st->drawcache) {
|
|
text_drawcache_init(st);
|
|
}
|
|
|
|
text_update_character_width(st);
|
|
|
|
drawcache = (DrawCache *)st->drawcache;
|
|
nlines = drawcache->nlines;
|
|
|
|
/* check if full cache update is needed */
|
|
|
|
/* area was resized */
|
|
full_update |= drawcache->winx != ar->winx;
|
|
/* word-wrapping option was toggled */
|
|
full_update |= drawcache->wordwrap != st->wordwrap;
|
|
/* word-wrapping option was toggled */
|
|
full_update |= drawcache->showlinenrs != st->showlinenrs;
|
|
/* word-wrapping option was toggled */
|
|
full_update |= drawcache->tabnumber != st->tabnumber;
|
|
/* word-wrapping option was toggled */
|
|
full_update |= drawcache->lheight != st->lheight_dpi;
|
|
/* word-wrapping option was toggled */
|
|
full_update |= drawcache->cwidth != st->cwidth;
|
|
/* text datablock was changed */
|
|
full_update |= !STREQLEN(drawcache->text_id, txt->id.name, MAX_ID_NAME);
|
|
|
|
if (st->wordwrap) {
|
|
/* update line heights */
|
|
if (full_update || !drawcache->line_height) {
|
|
drawcache->valid_head = 0;
|
|
drawcache->valid_tail = 0;
|
|
drawcache->update_flag = 1;
|
|
}
|
|
|
|
if (drawcache->update_flag) {
|
|
TextLine *line = st->text->lines.first;
|
|
int lineno = 0, size, lines_count;
|
|
int *fp = drawcache->line_height, *new_tail, *old_tail;
|
|
|
|
nlines = BLI_listbase_count(&txt->lines);
|
|
size = sizeof(int) * nlines;
|
|
|
|
if (fp) {
|
|
fp = MEM_reallocN(fp, size);
|
|
}
|
|
else {
|
|
fp = MEM_callocN(size, "text drawcache line_height");
|
|
}
|
|
|
|
drawcache->valid_tail = drawcache->valid_head = 0;
|
|
old_tail = fp + drawcache->nlines - drawcache->valid_tail;
|
|
new_tail = fp + nlines - drawcache->valid_tail;
|
|
memmove(new_tail, old_tail, drawcache->valid_tail);
|
|
|
|
drawcache->total_lines = 0;
|
|
|
|
if (st->showlinenrs) {
|
|
st->linenrs_tot = integer_digits_i(nlines);
|
|
}
|
|
|
|
while (line) {
|
|
if (drawcache->valid_head) { /* we're inside valid head lines */
|
|
lines_count = fp[lineno];
|
|
drawcache->valid_head--;
|
|
}
|
|
else if (lineno > new_tail - fp) { /* we-re inside valid tail lines */
|
|
lines_count = fp[lineno];
|
|
}
|
|
else {
|
|
lines_count = text_get_visible_lines(st, ar, line->line);
|
|
}
|
|
|
|
fp[lineno] = lines_count;
|
|
|
|
line = line->next;
|
|
lineno++;
|
|
drawcache->total_lines += lines_count;
|
|
}
|
|
|
|
drawcache->line_height = fp;
|
|
}
|
|
}
|
|
else {
|
|
if (drawcache->line_height) {
|
|
MEM_freeN(drawcache->line_height);
|
|
drawcache->line_height = NULL;
|
|
}
|
|
|
|
if (full_update || drawcache->update_flag) {
|
|
nlines = BLI_listbase_count(&txt->lines);
|
|
|
|
if (st->showlinenrs) {
|
|
st->linenrs_tot = integer_digits_i(nlines);
|
|
}
|
|
}
|
|
|
|
drawcache->total_lines = nlines;
|
|
}
|
|
|
|
drawcache->nlines = nlines;
|
|
|
|
/* store settings */
|
|
drawcache->winx = ar->winx;
|
|
drawcache->wordwrap = st->wordwrap;
|
|
drawcache->lheight = st->lheight_dpi;
|
|
drawcache->cwidth = st->cwidth;
|
|
drawcache->showlinenrs = st->showlinenrs;
|
|
drawcache->tabnumber = st->tabnumber;
|
|
|
|
strncpy(drawcache->text_id, txt->id.name, MAX_ID_NAME);
|
|
|
|
/* clear update flag */
|
|
drawcache->update_flag = 0;
|
|
drawcache->valid_head = 0;
|
|
drawcache->valid_tail = 0;
|
|
}
|
|
|
|
void text_drawcache_tag_update(SpaceText *st, int full)
|
|
{
|
|
/* this happens if text editor ops are caled from python */
|
|
if (st == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (st->drawcache) {
|
|
DrawCache *drawcache = (DrawCache *)st->drawcache;
|
|
Text *txt = st->text;
|
|
|
|
if (drawcache->update_flag) {
|
|
/* happens when tagging update from space listener */
|
|
/* should do nothing to prevent locally tagged cache be fully recalculated */
|
|
return;
|
|
}
|
|
|
|
if (!full) {
|
|
int sellno = BLI_findindex(&txt->lines, txt->sell);
|
|
int curlno = BLI_findindex(&txt->lines, txt->curl);
|
|
|
|
if (curlno < sellno) {
|
|
drawcache->valid_head = curlno;
|
|
drawcache->valid_tail = drawcache->nlines - sellno - 1;
|
|
}
|
|
else {
|
|
drawcache->valid_head = sellno;
|
|
drawcache->valid_tail = drawcache->nlines - curlno - 1;
|
|
}
|
|
|
|
/* quick cache recalculation is also used in delete operator,
|
|
* which could merge lines which are adjacent to current selection lines
|
|
* expand recalculate area to this lines */
|
|
if (drawcache->valid_head > 0) {
|
|
drawcache->valid_head--;
|
|
}
|
|
if (drawcache->valid_tail > 0) {
|
|
drawcache->valid_tail--;
|
|
}
|
|
}
|
|
else {
|
|
drawcache->valid_head = 0;
|
|
drawcache->valid_tail = 0;
|
|
}
|
|
|
|
drawcache->update_flag = 1;
|
|
}
|
|
}
|
|
|
|
void text_free_caches(SpaceText *st)
|
|
{
|
|
DrawCache *drawcache = (DrawCache *)st->drawcache;
|
|
|
|
if (drawcache) {
|
|
if (drawcache->line_height) {
|
|
MEM_freeN(drawcache->line_height);
|
|
}
|
|
|
|
MEM_freeN(drawcache);
|
|
}
|
|
}
|
|
|
|
/************************ word-wrap utilities *****************************/
|
|
|
|
/* cache should be updated in caller */
|
|
static int text_get_visible_lines_no(const SpaceText *st, int lineno)
|
|
{
|
|
const DrawCache *drawcache = st->drawcache;
|
|
|
|
return drawcache->line_height[lineno];
|
|
}
|
|
|
|
int text_get_visible_lines(const SpaceText *st, ARegion *ar, const char *str)
|
|
{
|
|
int i, j, start, end, max, lines, chars;
|
|
char ch;
|
|
|
|
max = wrap_width(st, ar);
|
|
lines = 1;
|
|
start = 0;
|
|
end = max;
|
|
for (i = 0, j = 0; str[j]; j += BLI_str_utf8_size_safe(str + j)) {
|
|
int columns = BLI_str_utf8_char_width_safe(str + j); /* = 1 for tab */
|
|
|
|
/* Mimic replacement of tabs */
|
|
ch = str[j];
|
|
if (ch == '\t') {
|
|
chars = st->tabnumber - i % st->tabnumber;
|
|
ch = ' ';
|
|
}
|
|
else {
|
|
chars = 1;
|
|
}
|
|
|
|
while (chars--) {
|
|
if (i + columns - start > max) {
|
|
lines++;
|
|
start = MIN2(end, i);
|
|
end += max;
|
|
}
|
|
else if (ch == ' ' || ch == '-') {
|
|
end = i + 1;
|
|
}
|
|
|
|
i += columns;
|
|
}
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
int text_get_span_wrap(const SpaceText *st, ARegion *ar, TextLine *from, TextLine *to)
|
|
{
|
|
if (st->wordwrap) {
|
|
int ret = 0;
|
|
TextLine *tmp = from;
|
|
|
|
/* Look forwards */
|
|
while (tmp) {
|
|
if (tmp == to) {
|
|
return ret;
|
|
}
|
|
ret += text_get_visible_lines(st, ar, tmp->line);
|
|
tmp = tmp->next;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
else {
|
|
return txt_get_span(from, to);
|
|
}
|
|
}
|
|
|
|
int text_get_total_lines(SpaceText *st, ARegion *ar)
|
|
{
|
|
DrawCache *drawcache;
|
|
|
|
text_update_drawcache(st, ar);
|
|
drawcache = st->drawcache;
|
|
|
|
return drawcache->total_lines;
|
|
}
|
|
|
|
/************************ draw scrollbar *****************************/
|
|
|
|
static void calc_text_rcts(SpaceText *st, ARegion *ar, rcti *scroll, rcti *back)
|
|
{
|
|
int lhlstart, lhlend, ltexth, sell_off, curl_off;
|
|
short barheight, barstart, hlstart, hlend, blank_lines;
|
|
short pix_available, pix_top_margin, pix_bottom_margin, pix_bardiff;
|
|
|
|
pix_top_margin = (0.4 * U.widget_unit);
|
|
pix_bottom_margin = (0.4 * U.widget_unit);
|
|
pix_available = ar->winy - pix_top_margin - pix_bottom_margin;
|
|
ltexth = text_get_total_lines(st, ar);
|
|
blank_lines = st->viewlines / 2;
|
|
|
|
/* nicer code: use scroll rect for entire bar */
|
|
back->xmin = ar->winx - (0.6 * U.widget_unit);
|
|
back->xmax = ar->winx;
|
|
back->ymin = 0;
|
|
back->ymax = ar->winy;
|
|
|
|
scroll->xmax = ar->winx - (0.2 * U.widget_unit);
|
|
scroll->xmin = scroll->xmax - (0.4 * U.widget_unit);
|
|
scroll->ymin = pix_top_margin;
|
|
scroll->ymax = pix_available;
|
|
|
|
/* when re-sizing a view-port with the bar at the bottom to a greater height
|
|
* more blank lines will be added */
|
|
if (ltexth + blank_lines < st->top + st->viewlines) {
|
|
blank_lines = st->top + st->viewlines - ltexth;
|
|
}
|
|
|
|
ltexth += blank_lines;
|
|
|
|
barheight = (ltexth > 0) ? (st->viewlines * pix_available) / ltexth : 0;
|
|
pix_bardiff = 0;
|
|
if (barheight < 20) {
|
|
pix_bardiff = 20 - barheight; /* take into account the now non-linear sizing of the bar */
|
|
barheight = 20;
|
|
}
|
|
barstart = (ltexth > 0) ? ((pix_available - pix_bardiff) * st->top) / ltexth : 0;
|
|
|
|
st->txtbar = *scroll;
|
|
st->txtbar.ymax -= barstart;
|
|
st->txtbar.ymin = st->txtbar.ymax - barheight;
|
|
|
|
CLAMP(st->txtbar.ymin, pix_bottom_margin, ar->winy - pix_top_margin);
|
|
CLAMP(st->txtbar.ymax, pix_bottom_margin, ar->winy - pix_top_margin);
|
|
|
|
st->pix_per_line = (pix_available > 0) ? (float)ltexth / pix_available : 0;
|
|
if (st->pix_per_line < 0.1f) {
|
|
st->pix_per_line = 0.1f;
|
|
}
|
|
|
|
curl_off = text_get_span_wrap(st, ar, st->text->lines.first, st->text->curl);
|
|
sell_off = text_get_span_wrap(st, ar, st->text->lines.first, st->text->sell);
|
|
lhlstart = MIN2(curl_off, sell_off);
|
|
lhlend = MAX2(curl_off, sell_off);
|
|
|
|
if (ltexth > 0) {
|
|
hlstart = (lhlstart * pix_available) / ltexth;
|
|
hlend = (lhlend * pix_available) / ltexth;
|
|
|
|
/* the scrollbar is non-linear sized */
|
|
if (pix_bardiff > 0) {
|
|
/* the start of the highlight is in the current viewport */
|
|
if (st->viewlines && lhlstart >= st->top && lhlstart <= st->top + st->viewlines) {
|
|
/* speed the progresion of the start of the highlight through the scrollbar */
|
|
hlstart = (((pix_available - pix_bardiff) * lhlstart) / ltexth) +
|
|
(pix_bardiff * (lhlstart - st->top) / st->viewlines);
|
|
}
|
|
else if (lhlstart > st->top + st->viewlines && hlstart < barstart + barheight &&
|
|
hlstart > barstart) {
|
|
/* push hl start down */
|
|
hlstart = barstart + barheight;
|
|
}
|
|
else if (lhlend > st->top && lhlstart < st->top && hlstart > barstart) {
|
|
/*fill out start */
|
|
hlstart = barstart;
|
|
}
|
|
|
|
if (hlend <= hlstart) {
|
|
hlend = hlstart + 2;
|
|
}
|
|
|
|
/* the end of the highlight is in the current viewport */
|
|
if (st->viewlines && lhlend >= st->top && lhlend <= st->top + st->viewlines) {
|
|
/* speed the progresion of the end of the highlight through the scrollbar */
|
|
hlend = (((pix_available - pix_bardiff) * lhlend) / ltexth) +
|
|
(pix_bardiff * (lhlend - st->top) / st->viewlines);
|
|
}
|
|
else if (lhlend < st->top && hlend >= barstart - 2 && hlend < barstart + barheight) {
|
|
/* push hl end up */
|
|
hlend = barstart;
|
|
}
|
|
else if (lhlend > st->top + st->viewlines && lhlstart < st->top + st->viewlines &&
|
|
hlend < barstart + barheight) {
|
|
/* fill out end */
|
|
hlend = barstart + barheight;
|
|
}
|
|
|
|
if (hlend <= hlstart) {
|
|
hlstart = hlend - 2;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
hlstart = 0;
|
|
hlend = 0;
|
|
}
|
|
|
|
if (hlend - hlstart < 2) {
|
|
hlend = hlstart + 2;
|
|
}
|
|
|
|
st->txtscroll = *scroll;
|
|
st->txtscroll.ymax = ar->winy - pix_top_margin - hlstart;
|
|
st->txtscroll.ymin = ar->winy - pix_top_margin - hlend;
|
|
|
|
CLAMP(st->txtscroll.ymin, pix_bottom_margin, ar->winy - pix_top_margin);
|
|
CLAMP(st->txtscroll.ymax, pix_bottom_margin, ar->winy - pix_top_margin);
|
|
}
|
|
|
|
static void draw_textscroll(const SpaceText *st, rcti *scroll, rcti *back)
|
|
{
|
|
bTheme *btheme = UI_GetTheme();
|
|
uiWidgetColors wcol = btheme->tui.wcol_scroll;
|
|
float col[4];
|
|
float rad;
|
|
|
|
/* background so highlights don't go behind the scrollbar */
|
|
uint pos = GPU_vertformat_attr_add(
|
|
immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
|
|
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
|
|
immUniformThemeColor(TH_BACK);
|
|
immRecti(pos, back->xmin, back->ymin, back->xmax, back->ymax);
|
|
immUnbindProgram();
|
|
|
|
UI_draw_widget_scroll(
|
|
&wcol, scroll, &st->txtbar, (st->flags & ST_SCROLL_SELECT) ? UI_SCROLL_PRESSED : 0);
|
|
|
|
UI_draw_roundbox_corner_set(UI_CNR_ALL);
|
|
rad = 0.4f * min_ii(BLI_rcti_size_x(&st->txtscroll), BLI_rcti_size_y(&st->txtscroll));
|
|
UI_GetThemeColor3fv(TH_HILITE, col);
|
|
col[3] = 0.18f;
|
|
UI_draw_roundbox_aa(true,
|
|
st->txtscroll.xmin + 1,
|
|
st->txtscroll.ymin,
|
|
st->txtscroll.xmax - 1,
|
|
st->txtscroll.ymax,
|
|
rad,
|
|
col);
|
|
}
|
|
|
|
/*********************** draw documentation *******************************/
|
|
|
|
#if 0
|
|
static void draw_documentation(const SpaceText *st, ARegion *ar)
|
|
{
|
|
TextDrawContext tdc = {0};
|
|
TextLine *tmp;
|
|
char *docs, buf[DOC_WIDTH + 1], *p;
|
|
int i, br, lines;
|
|
int boxw, boxh, l, x, y /* , top */ /* UNUSED */;
|
|
|
|
if (!st || !st->text) {
|
|
return;
|
|
}
|
|
if (!texttool_text_is_active(st->text)) {
|
|
return;
|
|
}
|
|
|
|
docs = texttool_docs_get();
|
|
|
|
if (!docs) {
|
|
return;
|
|
}
|
|
|
|
text_draw_context_init(st, &tdc);
|
|
|
|
/* Count the visible lines to the cursor */
|
|
for (tmp = st->text->curl, l = -st->top; tmp; tmp = tmp->prev, l++) {
|
|
/* pass */
|
|
}
|
|
|
|
if (l < 0) {
|
|
return;
|
|
}
|
|
|
|
if (st->showlinenrs) {
|
|
x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET + TEXTXLOC - 4;
|
|
}
|
|
else {
|
|
x = st->cwidth * (st->text->curc - st->left) + TXT_OFFSET - 4;
|
|
}
|
|
if (texttool_suggest_first()) {
|
|
x += SUGG_LIST_WIDTH * st->cwidth + 50;
|
|
}
|
|
|
|
/* top = */ /* UNUSED */ y = ar->winy - st->lheight_dpi * l - 2;
|
|
boxw = DOC_WIDTH * st->cwidth + 20;
|
|
boxh = (DOC_HEIGHT + 1) * (st->lheight_dpi + TXT_LINE_SPACING);
|
|
|
|
/* Draw panel */
|
|
uint pos = GPU_vertformat_attr_add(
|
|
immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
|
|
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
|
|
|
|
immUniformThemeColor(TH_BACK);
|
|
immRecti(pos, x, y, x + boxw, y - boxh);
|
|
immUniformThemeColor(TH_SHADE1);
|
|
immBegin(GPU_PRIM_LINE_LOOP, 4);
|
|
immVertex2i(pos, x, y);
|
|
immVertex2i(pos, x + boxw, y);
|
|
immVertex2i(pos, x + boxw, y - boxh);
|
|
immVertex2i(pos, x, y - boxh);
|
|
immEnd();
|
|
immBegin(GPU_PRIM_LINE_LOOP, 3);
|
|
immVertex2i(pos, x + boxw - 10, y - 7);
|
|
immVertex2i(pos, x + boxw - 4, y - 7);
|
|
immVertex2i(pos, x + boxw - 7, y - 2);
|
|
immEnd();
|
|
immBegin(GPU_PRIM_LINE_LOOP, 3);
|
|
immVertex2i(pos, x + boxw - 10, y - boxh + 7);
|
|
immVertex2i(pos, x + boxw - 4, y - boxh + 7);
|
|
immVertex2i(pos, x + boxw - 7, y - boxh + 2);
|
|
immEnd();
|
|
|
|
immUnbindProgram();
|
|
|
|
UI_FontThemeColor(tdc.font_id, TH_TEXT);
|
|
|
|
i = 0;
|
|
br = DOC_WIDTH;
|
|
lines = 0; // XXX -doc_scroll;
|
|
for (p = docs; *p; p++) {
|
|
if (*p == '\r' && *(++p) != '\n') {
|
|
*(--p) = '\n'; /* Fix line endings */
|
|
}
|
|
if (*p == ' ' || *p == '\t') {
|
|
br = i;
|
|
}
|
|
else if (*p == '\n') {
|
|
buf[i] = '\0';
|
|
if (lines >= 0) {
|
|
y -= st->lheight_dpi;
|
|
text_draw(st, &tdc, buf, 0, 0, x + 4, y - 3, NULL);
|
|
}
|
|
i = 0;
|
|
br = DOC_WIDTH;
|
|
lines++;
|
|
}
|
|
buf[i++] = *p;
|
|
if (i == DOC_WIDTH) { /* Reached the width, go to last break and wrap there */
|
|
buf[br] = '\0';
|
|
if (lines >= 0) {
|
|
y -= st->lheight_dpi;
|
|
text_draw(st, &tdc, buf, 0, 0, x + 4, y - 3, NULL);
|
|
}
|
|
p -= i - br - 1; /* Rewind pointer to last break */
|
|
i = 0;
|
|
br = DOC_WIDTH;
|
|
lines++;
|
|
}
|
|
if (lines >= DOC_HEIGHT) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*********************** draw suggestion list *******************************/
|
|
|
|
static void draw_suggestion_list(const SpaceText *st, const TextDrawContext *tdc, ARegion *ar)
|
|
{
|
|
SuggItem *item, *first, *last, *sel;
|
|
char str[SUGG_LIST_WIDTH * BLI_UTF8_MAX + 1];
|
|
int offl, offc, vcurl, vcurc;
|
|
int w, boxw = 0, boxh, i, x, y, *top;
|
|
const int lheight = st->lheight_dpi + TXT_LINE_SPACING;
|
|
const int margin_x = 2;
|
|
|
|
if (!st->text) {
|
|
return;
|
|
}
|
|
if (!texttool_text_is_active(st->text)) {
|
|
return;
|
|
}
|
|
|
|
first = texttool_suggest_first();
|
|
last = texttool_suggest_last();
|
|
|
|
if (!first || !last) {
|
|
return;
|
|
}
|
|
|
|
text_pop_suggest_list();
|
|
sel = texttool_suggest_selected();
|
|
top = texttool_suggest_top();
|
|
|
|
wrap_offset(st, ar, st->text->curl, st->text->curc, &offl, &offc);
|
|
vcurl = txt_get_span(st->text->lines.first, st->text->curl) - st->top + offl;
|
|
vcurc = text_get_char_pos(st, st->text->curl->line, st->text->curc) - st->left + offc;
|
|
|
|
x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
|
|
x += vcurc * st->cwidth - 4;
|
|
y = ar->winy - (vcurl + 1) * lheight - 2;
|
|
|
|
/* offset back so the start of the text lines up with the suggestions,
|
|
* not essential but makes suggestions easier to follow */
|
|
x -= st->cwidth *
|
|
(st->text->curc - text_find_identifier_start(st->text->curl->line, st->text->curc));
|
|
|
|
boxw = SUGG_LIST_WIDTH * st->cwidth + 20;
|
|
boxh = SUGG_LIST_SIZE * lheight + 8;
|
|
|
|
if (x + boxw > ar->winx) {
|
|
x = MAX2(0, ar->winx - boxw);
|
|
}
|
|
|
|
/* not needed but stands out nicer */
|
|
UI_draw_box_shadow(220, x, y - boxh, x + boxw, y);
|
|
|
|
uint pos = GPU_vertformat_attr_add(
|
|
immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
|
|
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
|
|
|
|
immUniformThemeColor(TH_SHADE1);
|
|
immRecti(pos, x - 1, y + 1, x + boxw + 1, y - boxh - 1);
|
|
immUniformThemeColorShade(TH_BACK, 16);
|
|
immRecti(pos, x, y, x + boxw, y - boxh);
|
|
|
|
immUnbindProgram();
|
|
|
|
/* Set the top 'item' of the visible list */
|
|
for (i = 0, item = first; i < *top && item->next; i++, item = item->next) {
|
|
/* pass */
|
|
}
|
|
|
|
for (i = 0; i < SUGG_LIST_SIZE && item; i++, item = item->next) {
|
|
int len = txt_utf8_forward_columns(item->name, SUGG_LIST_WIDTH, NULL) - item->name;
|
|
|
|
y -= lheight;
|
|
|
|
BLI_strncpy(str, item->name, len + 1);
|
|
|
|
w = st->cwidth * text_get_char_pos(st, str, len);
|
|
|
|
if (item == sel) {
|
|
uint posi = GPU_vertformat_attr_add(
|
|
immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
|
|
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
|
|
|
|
immUniformThemeColor(TH_SHADE2);
|
|
immRecti(posi, x + margin_x, y - 3, x + margin_x + w, y + lheight - 3);
|
|
|
|
immUnbindProgram();
|
|
}
|
|
|
|
format_draw_color(tdc, item->type);
|
|
text_draw(st, tdc, str, 0, 0, x + margin_x, y - 1, NULL);
|
|
|
|
if (item == last) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************** draw cursor ************************/
|
|
|
|
static void draw_text_decoration(SpaceText *st, ARegion *ar)
|
|
{
|
|
Text *text = st->text;
|
|
int vcurl, vcurc, vsell, vselc, hidden = 0;
|
|
int x, y, w, i;
|
|
int offl, offc;
|
|
const int lheight = st->lheight_dpi + TXT_LINE_SPACING;
|
|
|
|
/* Convert to view space character coordinates to determine if cursor is hidden */
|
|
wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
|
|
vsell = txt_get_span(text->lines.first, text->sell) - st->top + offl;
|
|
vselc = text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
|
|
|
|
if (vselc < 0) {
|
|
vselc = 0;
|
|
hidden = 1;
|
|
}
|
|
|
|
if (text->curl == text->sell && text->curc == text->selc && !st->line_hlight && hidden) {
|
|
/* Nothing to draw here */
|
|
return;
|
|
}
|
|
|
|
uint pos = GPU_vertformat_attr_add(
|
|
immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
|
|
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
|
|
|
|
/* Draw the selection */
|
|
if (text->curl != text->sell || text->curc != text->selc) {
|
|
/* Convert all to view space character coordinates */
|
|
wrap_offset(st, ar, text->curl, text->curc, &offl, &offc);
|
|
vcurl = txt_get_span(text->lines.first, text->curl) - st->top + offl;
|
|
vcurc = text_get_char_pos(st, text->curl->line, text->curc) - st->left + offc;
|
|
|
|
if (vcurc < 0) {
|
|
vcurc = 0;
|
|
}
|
|
|
|
immUniformThemeColor(TH_SHADE2);
|
|
|
|
x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
|
|
y = ar->winy;
|
|
|
|
if (vcurl == vsell) {
|
|
y -= vcurl * lheight;
|
|
|
|
if (vcurc < vselc) {
|
|
immRecti(pos, x + vcurc * st->cwidth - 1, y, x + vselc * st->cwidth, y - lheight);
|
|
}
|
|
else {
|
|
immRecti(pos, x + vselc * st->cwidth - 1, y, x + vcurc * st->cwidth, y - lheight);
|
|
}
|
|
}
|
|
else {
|
|
int froml, fromc, tol, toc;
|
|
|
|
if (vcurl < vsell) {
|
|
froml = vcurl;
|
|
tol = vsell;
|
|
fromc = vcurc;
|
|
toc = vselc;
|
|
}
|
|
else {
|
|
froml = vsell;
|
|
tol = vcurl;
|
|
fromc = vselc;
|
|
toc = vcurc;
|
|
}
|
|
|
|
y -= froml * lheight;
|
|
|
|
immRecti(pos, x + fromc * st->cwidth - 1, y, ar->winx, y - lheight);
|
|
y -= lheight;
|
|
|
|
for (i = froml + 1; i < tol; i++) {
|
|
immRecti(pos, x - 4, y, ar->winx, y - lheight);
|
|
y -= lheight;
|
|
}
|
|
|
|
immRecti(pos, x - 4, y, x + toc * st->cwidth, y - lheight);
|
|
y -= lheight;
|
|
}
|
|
}
|
|
|
|
if (st->line_hlight) {
|
|
int x1, x2, y1, y2;
|
|
|
|
if (st->wordwrap) {
|
|
int visible_lines = text_get_visible_lines(st, ar, text->sell->line);
|
|
|
|
wrap_offset_in_line(st, ar, text->sell, text->selc, &offl, &offc);
|
|
|
|
y1 = ar->winy - (vsell - offl) * lheight;
|
|
y2 = y1 - (lheight * visible_lines);
|
|
}
|
|
else {
|
|
y1 = ar->winy - vsell * lheight;
|
|
y2 = y1 - (lheight);
|
|
}
|
|
|
|
if (!(y1 < 0 || y2 > ar->winy)) { /* check we need to draw */
|
|
x1 = 0; // st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
|
|
x2 = x1 + ar->winx;
|
|
|
|
immUniformColor4ub(255, 255, 255, 32);
|
|
|
|
GPU_blend_set_func_separate(
|
|
GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
|
|
GPU_blend(true);
|
|
immRecti(pos, x1 - 4, y1, x2, y2);
|
|
GPU_blend(false);
|
|
}
|
|
}
|
|
|
|
if (!hidden) {
|
|
/* Draw the cursor itself (we draw the sel. cursor as this is the leading edge) */
|
|
x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
|
|
x += vselc * st->cwidth;
|
|
y = ar->winy - vsell * lheight;
|
|
|
|
immUniformThemeColor(TH_HILITE);
|
|
|
|
if (st->overwrite) {
|
|
char ch = text->sell->line[text->selc];
|
|
|
|
y += TXT_LINE_SPACING;
|
|
w = st->cwidth;
|
|
if (ch == '\t') {
|
|
w *= st->tabnumber - (vselc + st->left) % st->tabnumber;
|
|
}
|
|
|
|
immRecti(pos, x, y - lheight - 1, x + w, y - lheight + 1);
|
|
}
|
|
else {
|
|
immRecti(pos, x - 1, y, x + 1, y - lheight);
|
|
}
|
|
}
|
|
|
|
immUnbindProgram();
|
|
}
|
|
|
|
/******************* draw matching brackets *********************/
|
|
|
|
static void draw_brackets(const SpaceText *st, const TextDrawContext *tdc, ARegion *ar)
|
|
{
|
|
TextLine *startl, *endl, *linep;
|
|
Text *text = st->text;
|
|
int b, fc, find, stack, viewc, viewl, offl, offc, x, y;
|
|
int startc, endc, c;
|
|
|
|
char ch;
|
|
|
|
// syntax_highlight must be on or else the format string will be null
|
|
if (!text->curl || !tdc->syntax_highlight) {
|
|
return;
|
|
}
|
|
|
|
startl = text->curl;
|
|
startc = text->curc;
|
|
b = text_check_bracket(startl->line[startc]);
|
|
if (b == 0 && startc > 0) {
|
|
b = text_check_bracket(startl->line[--startc]);
|
|
}
|
|
if (b == 0) {
|
|
return;
|
|
}
|
|
|
|
linep = startl;
|
|
c = startc;
|
|
fc = BLI_str_utf8_offset_to_index(linep->line, startc);
|
|
endl = NULL;
|
|
endc = -1;
|
|
find = -b;
|
|
stack = 0;
|
|
|
|
/* Don't highlight backets if syntax HL is off or bracket in string or comment. */
|
|
if (!linep->format || linep->format[fc] == FMT_TYPE_STRING ||
|
|
linep->format[fc] == FMT_TYPE_COMMENT) {
|
|
return;
|
|
}
|
|
|
|
if (b > 0) {
|
|
/* opening bracket, search forward for close */
|
|
fc++;
|
|
c += BLI_str_utf8_size_safe(linep->line + c);
|
|
while (linep) {
|
|
while (c < linep->len) {
|
|
if (linep->format && linep->format[fc] != FMT_TYPE_STRING &&
|
|
linep->format[fc] != FMT_TYPE_COMMENT) {
|
|
b = text_check_bracket(linep->line[c]);
|
|
if (b == find) {
|
|
if (stack == 0) {
|
|
endl = linep;
|
|
endc = c;
|
|
break;
|
|
}
|
|
stack--;
|
|
}
|
|
else if (b == -find) {
|
|
stack++;
|
|
}
|
|
}
|
|
fc++;
|
|
c += BLI_str_utf8_size_safe(linep->line + c);
|
|
}
|
|
if (endl) {
|
|
break;
|
|
}
|
|
linep = linep->next;
|
|
c = 0;
|
|
fc = 0;
|
|
}
|
|
}
|
|
else {
|
|
/* closing bracket, search backward for open */
|
|
fc--;
|
|
if (c > 0) {
|
|
c -= linep->line + c - BLI_str_prev_char_utf8(linep->line + c);
|
|
}
|
|
while (linep) {
|
|
while (fc >= 0) {
|
|
if (linep->format && linep->format[fc] != FMT_TYPE_STRING &&
|
|
linep->format[fc] != FMT_TYPE_COMMENT) {
|
|
b = text_check_bracket(linep->line[c]);
|
|
if (b == find) {
|
|
if (stack == 0) {
|
|
endl = linep;
|
|
endc = c;
|
|
break;
|
|
}
|
|
stack--;
|
|
}
|
|
else if (b == -find) {
|
|
stack++;
|
|
}
|
|
}
|
|
fc--;
|
|
if (c > 0) {
|
|
c -= linep->line + c - BLI_str_prev_char_utf8(linep->line + c);
|
|
}
|
|
}
|
|
if (endl) {
|
|
break;
|
|
}
|
|
linep = linep->prev;
|
|
if (linep) {
|
|
if (linep->format) {
|
|
fc = strlen(linep->format) - 1;
|
|
}
|
|
else {
|
|
fc = -1;
|
|
}
|
|
if (linep->len) {
|
|
c = BLI_str_prev_char_utf8(linep->line + linep->len) - linep->line;
|
|
}
|
|
else {
|
|
fc = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!endl || endc == -1) {
|
|
return;
|
|
}
|
|
|
|
UI_FontThemeColor(tdc->font_id, TH_HILITE);
|
|
x = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
|
|
y = ar->winy - st->lheight_dpi;
|
|
|
|
/* draw opening bracket */
|
|
ch = startl->line[startc];
|
|
wrap_offset(st, ar, startl, startc, &offl, &offc);
|
|
viewc = text_get_char_pos(st, startl->line, startc) - st->left + offc;
|
|
|
|
if (viewc >= 0) {
|
|
viewl = txt_get_span(text->lines.first, startl) - st->top + offl;
|
|
|
|
text_font_draw_character(
|
|
tdc, x + viewc * st->cwidth, y - viewl * (st->lheight_dpi + TXT_LINE_SPACING), ch);
|
|
text_font_draw_character(
|
|
tdc, x + viewc * st->cwidth + 1, y - viewl * (st->lheight_dpi + TXT_LINE_SPACING), ch);
|
|
}
|
|
|
|
/* draw closing bracket */
|
|
ch = endl->line[endc];
|
|
wrap_offset(st, ar, endl, endc, &offl, &offc);
|
|
viewc = text_get_char_pos(st, endl->line, endc) - st->left + offc;
|
|
|
|
if (viewc >= 0) {
|
|
viewl = txt_get_span(text->lines.first, endl) - st->top + offl;
|
|
|
|
text_font_draw_character(
|
|
tdc, x + viewc * st->cwidth, y - viewl * (st->lheight_dpi + TXT_LINE_SPACING), ch);
|
|
text_font_draw_character(
|
|
tdc, x + viewc * st->cwidth + 1, y - viewl * (st->lheight_dpi + TXT_LINE_SPACING), ch);
|
|
}
|
|
}
|
|
|
|
/*********************** main region drawing *************************/
|
|
|
|
void draw_text_main(SpaceText *st, ARegion *ar)
|
|
{
|
|
TextDrawContext tdc = {0};
|
|
Text *text = st->text;
|
|
TextFormatType *tft;
|
|
TextLine *tmp;
|
|
rcti scroll, back;
|
|
char linenr[12];
|
|
int i, x, y, winx, linecount = 0, lineno = 0;
|
|
int wraplinecount = 0, wrap_skip = 0;
|
|
int margin_column_x;
|
|
|
|
/* if no text, nothing to do */
|
|
if (!text) {
|
|
return;
|
|
}
|
|
|
|
/* dpi controlled line height and font size */
|
|
st->lheight_dpi = (U.widget_unit * st->lheight) / 20;
|
|
|
|
/* don't draw lines below this */
|
|
const int clip_min_y = -(int)(st->lheight_dpi - 1);
|
|
|
|
st->viewlines = (st->lheight_dpi) ?
|
|
(int)(ar->winy - clip_min_y) / (st->lheight_dpi + TXT_LINE_SPACING) :
|
|
0;
|
|
|
|
text_draw_context_init(st, &tdc);
|
|
|
|
text_update_drawcache(st, ar);
|
|
|
|
/* make sure all the positional pointers exist */
|
|
if (!text->curl || !text->sell || !text->lines.first || !text->lines.last) {
|
|
txt_clean_text(text);
|
|
}
|
|
|
|
/* update rects for scroll */
|
|
calc_text_rcts(st, ar, &scroll, &back); /* scroll will hold the entire bar size */
|
|
|
|
/* update syntax formatting if needed */
|
|
tft = ED_text_format_get(text);
|
|
tmp = text->lines.first;
|
|
lineno = 0;
|
|
for (i = 0; i < st->top && tmp; i++) {
|
|
if (tdc.syntax_highlight && !tmp->format) {
|
|
tft->format_line(st, tmp, false);
|
|
}
|
|
|
|
if (st->wordwrap) {
|
|
int lines = text_get_visible_lines_no(st, lineno);
|
|
|
|
if (wraplinecount + lines > st->top) {
|
|
wrap_skip = st->top - wraplinecount;
|
|
break;
|
|
}
|
|
else {
|
|
wraplinecount += lines;
|
|
tmp = tmp->next;
|
|
linecount++;
|
|
}
|
|
}
|
|
else {
|
|
tmp = tmp->next;
|
|
linecount++;
|
|
}
|
|
|
|
lineno++;
|
|
}
|
|
|
|
text_font_begin(&tdc);
|
|
|
|
tdc.cwidth = max_ii((int)BLF_fixed_width(tdc.font_id), 1);
|
|
st->cwidth = tdc.cwidth;
|
|
|
|
/* draw line numbers background */
|
|
if (st->showlinenrs) {
|
|
x = TXT_OFFSET + TEXTXLOC;
|
|
|
|
uint pos = GPU_vertformat_attr_add(
|
|
immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
|
|
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
|
|
immUniformThemeColor(TH_GRID);
|
|
immRecti(pos, (TXT_OFFSET - 12), 0, (TXT_OFFSET - 5) + TEXTXLOC, ar->winy - 2);
|
|
immUnbindProgram();
|
|
}
|
|
else {
|
|
st->linenrs_tot = 0; /* not used */
|
|
x = TXT_OFFSET;
|
|
}
|
|
y = ar->winy - st->lheight_dpi;
|
|
winx = ar->winx - TXT_SCROLL_WIDTH;
|
|
|
|
/* draw cursor, margin, selection and highlight */
|
|
draw_text_decoration(st, ar);
|
|
|
|
/* draw the text */
|
|
UI_FontThemeColor(tdc.font_id, TH_TEXT);
|
|
|
|
for (i = 0; y > clip_min_y && i < st->viewlines && tmp; i++, tmp = tmp->next) {
|
|
if (tdc.syntax_highlight && !tmp->format) {
|
|
tft->format_line(st, tmp, false);
|
|
}
|
|
|
|
if (st->showlinenrs && !wrap_skip) {
|
|
/* draw line number */
|
|
if (tmp == text->curl) {
|
|
UI_FontThemeColor(tdc.font_id, TH_HILITE);
|
|
}
|
|
else {
|
|
UI_FontThemeColor(tdc.font_id, TH_TEXT);
|
|
}
|
|
|
|
BLI_snprintf(linenr, sizeof(linenr), "%*d", st->linenrs_tot, i + linecount + 1);
|
|
/* itoa(i + linecount + 1, linenr, 10); */ /* not ansi-c :/ */
|
|
text_font_draw(&tdc, TXT_OFFSET - 7, y, linenr);
|
|
|
|
if (tmp == text->curl) {
|
|
UI_FontThemeColor(tdc.font_id, TH_TEXT);
|
|
}
|
|
}
|
|
|
|
if (st->wordwrap) {
|
|
/* draw word wrapped text */
|
|
int lines = text_draw_wrapped(st, &tdc, tmp->line, x, y, winx - x, tmp->format, wrap_skip);
|
|
y -= lines * (st->lheight_dpi + TXT_LINE_SPACING);
|
|
}
|
|
else {
|
|
/* draw unwrapped text */
|
|
text_draw(st, &tdc, tmp->line, st->left, ar->winx / st->cwidth, x, y, tmp->format);
|
|
y -= st->lheight_dpi + TXT_LINE_SPACING;
|
|
}
|
|
|
|
wrap_skip = 0;
|
|
}
|
|
|
|
if (st->flags & ST_SHOW_MARGIN) {
|
|
margin_column_x = x + st->cwidth * (st->margin_column - st->left);
|
|
|
|
if (margin_column_x >= x) {
|
|
const uint shdr_pos = GPU_vertformat_attr_add(
|
|
immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
|
|
|
|
immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
|
|
|
|
GPU_line_width(2.0f);
|
|
|
|
float viewport_size[4];
|
|
GPU_viewport_size_get_f(viewport_size);
|
|
immUniform2f("viewport_size", viewport_size[2] / UI_DPI_FAC, viewport_size[3] / UI_DPI_FAC);
|
|
|
|
immUniform1i("colors_len", 0); /* "simple" mode */
|
|
immUniformThemeColor3(TH_GRID); /* same color as line number background */
|
|
immUniform1f("dash_width", 2.0f);
|
|
immUniform1f("dash_factor", 0.5f);
|
|
|
|
immBegin(GPU_PRIM_LINES, 2);
|
|
immVertex2i(shdr_pos, margin_column_x, 0);
|
|
immVertex2i(shdr_pos, margin_column_x, ar->winy - 2);
|
|
immEnd();
|
|
immUnbindProgram();
|
|
}
|
|
}
|
|
|
|
/* draw other stuff */
|
|
draw_brackets(st, &tdc, ar);
|
|
draw_textscroll(st, &scroll, &back);
|
|
/* draw_documentation(st, ar); - No longer supported */
|
|
draw_suggestion_list(st, &tdc, ar);
|
|
|
|
text_font_end(&tdc);
|
|
}
|
|
|
|
/************************** update ***************************/
|
|
|
|
void text_update_character_width(SpaceText *st)
|
|
{
|
|
TextDrawContext tdc = {0};
|
|
|
|
text_draw_context_init(st, &tdc);
|
|
|
|
text_font_begin(&tdc);
|
|
st->cwidth = BLF_fixed_width(tdc.font_id);
|
|
st->cwidth = MAX2(st->cwidth, (char)1);
|
|
text_font_end(&tdc);
|
|
}
|
|
|
|
/* Moves the view to the cursor location,
|
|
* also used to make sure the view isn't outside the file */
|
|
void text_scroll_to_cursor(SpaceText *st, ARegion *ar, const bool center)
|
|
{
|
|
Text *text;
|
|
int i, x, winx = ar->winx;
|
|
|
|
if (ELEM(NULL, st, st->text, st->text->curl)) {
|
|
return;
|
|
}
|
|
|
|
text = st->text;
|
|
|
|
text_update_character_width(st);
|
|
|
|
i = txt_get_span(text->lines.first, text->sell);
|
|
if (st->wordwrap) {
|
|
int offl, offc;
|
|
wrap_offset(st, ar, text->sell, text->selc, &offl, &offc);
|
|
i += offl;
|
|
}
|
|
|
|
if (center) {
|
|
if (st->top + st->viewlines <= i || st->top > i) {
|
|
st->top = i - st->viewlines / 2;
|
|
}
|
|
}
|
|
else {
|
|
if (st->top + st->viewlines <= i) {
|
|
st->top = i - (st->viewlines - 1);
|
|
}
|
|
else if (st->top > i) {
|
|
st->top = i;
|
|
}
|
|
}
|
|
|
|
if (st->wordwrap) {
|
|
st->left = 0;
|
|
}
|
|
else {
|
|
x = st->cwidth * (text_get_char_pos(st, text->sell->line, text->selc) - st->left);
|
|
winx -= TXT_OFFSET + (st->showlinenrs ? TEXTXLOC : 0) + TXT_SCROLL_WIDTH;
|
|
|
|
if (center) {
|
|
if (x <= 0 || x > winx) {
|
|
st->left += (x - winx / 2) / st->cwidth;
|
|
}
|
|
}
|
|
else {
|
|
if (x <= 0) {
|
|
st->left += ((x + 1) / st->cwidth) - 1;
|
|
}
|
|
else if (x > winx) {
|
|
st->left += ((x - (winx + 1)) / st->cwidth) + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (st->top < 0) {
|
|
st->top = 0;
|
|
}
|
|
if (st->left < 0) {
|
|
st->left = 0;
|
|
}
|
|
|
|
st->scroll_accum[0] = 0.0f;
|
|
st->scroll_accum[1] = 0.0f;
|
|
}
|
|
|
|
/* takes an area instead of a region, use for listeners */
|
|
void text_scroll_to_cursor__area(SpaceText *st, ScrArea *sa, const bool center)
|
|
{
|
|
ARegion *ar;
|
|
|
|
if (ELEM(NULL, st, st->text, st->text->curl)) {
|
|
return;
|
|
}
|
|
|
|
ar = BKE_area_find_region_type(sa, RGN_TYPE_WINDOW);
|
|
|
|
if (ar) {
|
|
text_scroll_to_cursor(st, ar, center);
|
|
}
|
|
}
|
|
|
|
void text_update_cursor_moved(bContext *C)
|
|
{
|
|
ScrArea *sa = CTX_wm_area(C);
|
|
SpaceText *st = CTX_wm_space_text(C);
|
|
|
|
text_scroll_to_cursor__area(st, sa, true);
|
|
}
|
|
|
|
/**
|
|
* Takes a cursor (row, character) and returns x,y pixel coords.
|
|
*/
|
|
bool ED_text_region_location_from_cursor(SpaceText *st,
|
|
ARegion *ar,
|
|
const int cursor_co[2],
|
|
int r_pixel_co[2])
|
|
{
|
|
TextLine *line = NULL;
|
|
|
|
if (!st->text) {
|
|
goto error;
|
|
}
|
|
|
|
line = BLI_findlink(&st->text->lines, cursor_co[0]);
|
|
if (!line || (cursor_co[1] < 0) || (cursor_co[1] > line->len)) {
|
|
goto error;
|
|
}
|
|
else {
|
|
int offl, offc;
|
|
int linenr_offset = st->showlinenrs ? TXT_OFFSET + TEXTXLOC : TXT_OFFSET;
|
|
/* handle tabs as well! */
|
|
int char_pos = text_get_char_pos(st, line->line, cursor_co[1]);
|
|
|
|
wrap_offset(st, ar, line, cursor_co[1], &offl, &offc);
|
|
r_pixel_co[0] = (char_pos + offc - st->left) * st->cwidth + linenr_offset;
|
|
r_pixel_co[1] = (cursor_co[0] + offl - st->top) * (st->lheight_dpi + TXT_LINE_SPACING);
|
|
r_pixel_co[1] = (ar->winy - (r_pixel_co[1] + TXT_OFFSET)) - st->lheight_dpi;
|
|
}
|
|
return true;
|
|
|
|
error:
|
|
r_pixel_co[0] = r_pixel_co[1] = -1;
|
|
return false;
|
|
}
|