Without this it was necessary to convert pasted text into spaces for them to display properly. Since tabs are a valid part of a string, it's incorrect to assume tabs can always be expanded to spaces. Pasted text and console output now display tabs properly, the tab still uses spaces for indenting though.
256 lines
7.4 KiB
C++
256 lines
7.4 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup spconsole
|
|
*/
|
|
|
|
#include <cstring>
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_screen_types.h"
|
|
#include "DNA_space_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "GPU_immediate.h"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_resources.hh"
|
|
#include "UI_view2d.hh"
|
|
|
|
#include "console_intern.hh"
|
|
|
|
#include "../space_info/textview.hh"
|
|
|
|
static enum eTextViewContext_LineFlag console_line_data(TextViewContext *tvc,
|
|
uchar fg[4],
|
|
uchar /*bg*/[4],
|
|
int * /*icon*/,
|
|
uchar /*icon_fg*/[4],
|
|
uchar /*icon_bg*/[4])
|
|
{
|
|
const ConsoleLine *cl_iter = static_cast<const ConsoleLine *>(tvc->iter);
|
|
int fg_id = TH_TEXT;
|
|
|
|
switch (cl_iter->type) {
|
|
case CONSOLE_LINE_OUTPUT:
|
|
fg_id = TH_CONSOLE_OUTPUT;
|
|
break;
|
|
case CONSOLE_LINE_INPUT:
|
|
fg_id = TH_CONSOLE_INPUT;
|
|
break;
|
|
case CONSOLE_LINE_INFO:
|
|
fg_id = TH_CONSOLE_INFO;
|
|
break;
|
|
case CONSOLE_LINE_ERROR:
|
|
fg_id = TH_CONSOLE_ERROR;
|
|
break;
|
|
}
|
|
|
|
UI_GetThemeColor4ubv(fg_id, fg);
|
|
return TVC_LINE_FG;
|
|
}
|
|
|
|
void console_scrollback_prompt_begin(SpaceConsole *sc, ConsoleLine *cl_dummy)
|
|
{
|
|
/* fake the edit line being in the scroll buffer */
|
|
ConsoleLine *cl = static_cast<ConsoleLine *>(sc->history.last);
|
|
int prompt_len = strlen(sc->prompt);
|
|
|
|
cl_dummy->type = CONSOLE_LINE_INPUT;
|
|
cl_dummy->len = prompt_len + cl->len;
|
|
cl_dummy->len_alloc = cl_dummy->len + 1;
|
|
cl_dummy->line = static_cast<char *>(MEM_mallocN(cl_dummy->len_alloc, "cl_dummy"));
|
|
memcpy(cl_dummy->line, sc->prompt, prompt_len);
|
|
memcpy(cl_dummy->line + prompt_len, cl->line, cl->len + 1);
|
|
BLI_addtail(&sc->scrollback, cl_dummy);
|
|
}
|
|
void console_scrollback_prompt_end(SpaceConsole *sc, ConsoleLine *cl_dummy)
|
|
{
|
|
MEM_freeN(cl_dummy->line);
|
|
BLI_remlink(&sc->scrollback, cl_dummy);
|
|
}
|
|
|
|
/* console textview callbacks */
|
|
static int console_textview_begin(TextViewContext *tvc)
|
|
{
|
|
SpaceConsole *sc = (SpaceConsole *)tvc->arg1;
|
|
tvc->sel_start = sc->sel_start;
|
|
tvc->sel_end = sc->sel_end;
|
|
|
|
/* iterator */
|
|
tvc->iter = sc->scrollback.last;
|
|
|
|
return (tvc->iter != nullptr);
|
|
}
|
|
|
|
static void console_textview_end(TextViewContext *tvc)
|
|
{
|
|
SpaceConsole *sc = (SpaceConsole *)tvc->arg1;
|
|
(void)sc;
|
|
}
|
|
|
|
static int console_textview_step(TextViewContext *tvc)
|
|
{
|
|
return ((tvc->iter = (void *)((Link *)tvc->iter)->prev) != nullptr);
|
|
}
|
|
|
|
static void console_textview_line_get(TextViewContext *tvc, const char **r_line, int *r_len)
|
|
{
|
|
const ConsoleLine *cl = static_cast<const ConsoleLine *>(tvc->iter);
|
|
*r_line = cl->line;
|
|
*r_len = cl->len;
|
|
// printf("'%s' %d\n", *line, cl->len);
|
|
BLI_assert(cl->line[cl->len] == '\0' && (cl->len == 0 || cl->line[cl->len - 1] != '\0'));
|
|
}
|
|
|
|
static void console_cursor_wrap_offset(
|
|
const char *str, int width, int *row, int *column, const char *end)
|
|
{
|
|
int col;
|
|
const int tab_width = 4;
|
|
|
|
for (; *str; str += BLI_str_utf8_size_safe(str)) {
|
|
col = UNLIKELY(*str == '\t') ? (tab_width - (*column % tab_width)) :
|
|
BLI_str_utf8_char_width_safe(str);
|
|
|
|
if (*column + col > width) {
|
|
(*row)++;
|
|
*column = 0;
|
|
}
|
|
|
|
if (end && str >= end) {
|
|
break;
|
|
}
|
|
|
|
*column += col;
|
|
}
|
|
}
|
|
|
|
static void console_textview_draw_cursor(TextViewContext *tvc, int cwidth, int columns)
|
|
{
|
|
int pen[2];
|
|
{
|
|
const SpaceConsole *sc = (SpaceConsole *)tvc->arg1;
|
|
const ConsoleLine *cl = (ConsoleLine *)sc->history.last;
|
|
int offl = 0, offc = 0;
|
|
|
|
console_cursor_wrap_offset(sc->prompt, columns, &offl, &offc, nullptr);
|
|
console_cursor_wrap_offset(cl->line, columns, &offl, &offc, cl->line + cl->cursor);
|
|
pen[0] = cwidth * offc;
|
|
pen[1] = -tvc->lheight * offl;
|
|
|
|
console_cursor_wrap_offset(cl->line + cl->cursor, columns, &offl, &offc, nullptr);
|
|
pen[1] += tvc->lheight * offl;
|
|
|
|
pen[0] += tvc->draw_rect.xmin;
|
|
pen[1] += tvc->draw_rect.ymin;
|
|
}
|
|
|
|
/* cursor */
|
|
GPUVertFormat *format = immVertexFormat();
|
|
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
|
|
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
|
|
immUniformThemeColor(TH_CONSOLE_CURSOR);
|
|
|
|
immRectf(pos, pen[0] - U.pixelsize, pen[1], pen[0] + U.pixelsize, pen[1] + tvc->lheight);
|
|
|
|
immUnbindProgram();
|
|
}
|
|
|
|
static void console_textview_const_colors(TextViewContext * /*tvc*/, uchar bg_sel[4])
|
|
{
|
|
UI_GetThemeColor4ubv(TH_CONSOLE_SELECT, bg_sel);
|
|
}
|
|
|
|
static void console_textview_draw_rect_calc(const ARegion *region,
|
|
rcti *r_draw_rect,
|
|
rcti *r_draw_rect_outer)
|
|
{
|
|
const int margin = 4 * UI_SCALE_FAC;
|
|
r_draw_rect->xmin = margin;
|
|
r_draw_rect->xmax = region->winx - V2D_SCROLL_WIDTH;
|
|
r_draw_rect->ymin = margin;
|
|
/* No margin at the top (allow text to scroll off the window). */
|
|
r_draw_rect->ymax = region->winy;
|
|
|
|
r_draw_rect_outer->xmin = 0;
|
|
r_draw_rect_outer->xmax = region->winx;
|
|
r_draw_rect_outer->ymin = 0;
|
|
r_draw_rect_outer->ymax = region->winy;
|
|
}
|
|
|
|
static int console_textview_main__internal(SpaceConsole *sc,
|
|
const ARegion *region,
|
|
const bool do_draw,
|
|
const int mval[2],
|
|
void **r_mval_pick_item,
|
|
int *r_mval_pick_offset)
|
|
{
|
|
ConsoleLine cl_dummy = {nullptr};
|
|
int ret = 0;
|
|
|
|
const View2D *v2d = ®ion->v2d;
|
|
|
|
TextViewContext tvc = {0};
|
|
|
|
tvc.begin = console_textview_begin;
|
|
tvc.end = console_textview_end;
|
|
|
|
tvc.step = console_textview_step;
|
|
tvc.line_get = console_textview_line_get;
|
|
tvc.line_data = console_line_data;
|
|
tvc.draw_cursor = console_textview_draw_cursor;
|
|
tvc.const_colors = console_textview_const_colors;
|
|
|
|
tvc.arg1 = sc;
|
|
tvc.arg2 = nullptr;
|
|
|
|
/* view */
|
|
tvc.sel_start = sc->sel_start;
|
|
tvc.sel_end = sc->sel_end;
|
|
tvc.lheight = sc->lheight * UI_SCALE_FAC;
|
|
tvc.scroll_ymin = v2d->cur.ymin;
|
|
tvc.scroll_ymax = v2d->cur.ymax;
|
|
|
|
console_textview_draw_rect_calc(region, &tvc.draw_rect, &tvc.draw_rect_outer);
|
|
|
|
/* Nudge right by half a column to break selection mid-character. */
|
|
int m_pos[2] = {mval[0], mval[1]};
|
|
/* Mouse position is initialized with max int. */
|
|
if (m_pos[0] != INT_MAX) {
|
|
m_pos[0] += tvc.lheight / 4;
|
|
}
|
|
|
|
console_scrollback_prompt_begin(sc, &cl_dummy);
|
|
ret = textview_draw(&tvc, do_draw, m_pos, r_mval_pick_item, r_mval_pick_offset);
|
|
console_scrollback_prompt_end(sc, &cl_dummy);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void console_textview_main(SpaceConsole *sc, const ARegion *region)
|
|
{
|
|
const int mval[2] = {INT_MAX, INT_MAX};
|
|
console_textview_main__internal(sc, region, true, mval, nullptr, nullptr);
|
|
}
|
|
|
|
int console_textview_height(SpaceConsole *sc, const ARegion *region)
|
|
{
|
|
const int mval[2] = {INT_MAX, INT_MAX};
|
|
return console_textview_main__internal(sc, region, false, mval, nullptr, nullptr);
|
|
}
|
|
|
|
int console_char_pick(SpaceConsole *sc, const ARegion *region, const int mval[2])
|
|
{
|
|
int r_mval_pick_offset = 0;
|
|
void *mval_pick_item = nullptr;
|
|
|
|
console_textview_main__internal(sc, region, false, mval, &mval_pick_item, &r_mval_pick_offset);
|
|
return r_mval_pick_offset;
|
|
}
|