summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Lihatskiy <118752495+marchcat@users.noreply.github.com>2026-01-15 13:18:02 +0200
committerGitHub <noreply@github.com>2026-01-15 13:18:02 +0200
commitc2e0c724868d0d36ead0dadf15d087501268b713 (patch)
tree20b3adfd59f83c9080e6a437aec100f27759108a
parent6639992a0fc822efd1d6deb1cadf20f6d9a8b037 (diff)
parentd86785b28a69d2a10808e5dc2f2d0588a7be5768 (diff)
Merge pull request #5279 from secondlife/marchcat/slua-thread-5278
Async SLua parsing
-rw-r--r--indra/llui/llkeywords.cpp139
-rw-r--r--indra/llui/llkeywords.h28
-rw-r--r--indra/newview/llscripteditor.cpp406
-rw-r--r--indra/newview/llscripteditor.h40
4 files changed, 550 insertions, 63 deletions
diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp
index 2168ba2382..fd9991fd11 100644
--- a/indra/llui/llkeywords.cpp
+++ b/indra/llui/llkeywords.cpp
@@ -497,42 +497,29 @@ bool LLKeywords::WStringMapIndex::operator<(const LLKeywords::WStringMapIndex &o
}
LLTrace::BlockTimerStatHandle FTM_SYNTAX_COLORING("Syntax Coloring");
+constexpr size_t AVERAGE_SEGMENT_LENGTH = 8;
-// Walk through a string, applying the rules specified by the keyword token list and
-// create a list of color segments.
-void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLWString& wtext, LLTextEditor& editor, LLStyleConstSP style)
+void LLKeywords::collectSegmentOps(segment_ops_t& ops, const LLWString& wtext, bool disable_syntax_highlighting) const
{
- LL_RECORD_BLOCK_TIME(FTM_SYNTAX_COLORING);
+ ops.clear();
- if( wtext.empty() )
+ if (wtext.empty())
{
return;
}
-
- // Clear the segment list
- seg_list->clear();
- // Reserve capacity for segments based on an estimated average of 8 characters per segment.
- constexpr size_t AVERAGE_SEGMENT_LENGTH = 8;
- seg_list->reserve(wtext.size() / AVERAGE_SEGMENT_LENGTH);
-
- S32 text_len = static_cast<S32>(wtext.size()) + 1;
-
- seg_list->push_back( new LLNormalTextSegment( style, 0, text_len, editor ) );
-
- static LLCachedControl<bool> sDisableSyntaxHighlighting(gSavedSettings, "ScriptEditorDisableSyntaxHighlight", false);
- const bool disable_syntax_highlighting = sDisableSyntaxHighlighting;
+ // Heuristic to reduce reallocation churn on large scripts.
+ ops.reserve(wtext.size() / AVERAGE_SEGMENT_LENGTH);
const llwchar* base = wtext.c_str();
const llwchar* cur = base;
+
while( *cur )
{
if( *cur == '\n' || cur == base )
{
if( *cur == '\n' )
{
- LLTextSegmentPtr text_segment = new LLLineBreakTextSegment(style, (S32)(cur - base));
- text_segment->setToken( 0 );
- insertSegment( *seg_list, text_segment, text_len, style, editor);
+ ops.push_back({SegmentOp::OP_LINE_BREAK, (S32)(cur - base), 0, nullptr});
cur++;
if( !*cur || *cur == '\n' )
{
@@ -570,8 +557,7 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW
}
S32 seg_end = (S32)(cur - base);
- //create segments from seg_start to seg_end
- insertSegments(wtext, *seg_list,cur_token, text_len, seg_start, seg_end, style, editor);
+ ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, cur_token});
line_done = true; // to break out of second loop.
break;
}
@@ -594,9 +580,9 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW
// Check if syntax highlighting is disabled
if (disable_syntax_highlighting)
{
- if (*cur && *cur != '\n')
+ while (*cur && *cur != '\n')
{
- cur++;
+ ++cur;
}
continue; // skip processing any further syntax highlighting
}
@@ -646,7 +632,6 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW
if (*p == '[')
{
p++; // skip the second [
- const llwchar* content_start = p;
// Build the closing pattern: ] + level equals + ]
// Search for it in the remaining text
@@ -667,7 +652,7 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW
// Found the matching close
seg_end = (S32)(close_check + 1 - base);
cur = close_check + 1;
- insertSegments(wtext, *seg_list, cur_delimiter, text_len, seg_start, seg_end, style, editor);
+ ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, cur_delimiter});
break;
}
}
@@ -677,9 +662,9 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW
if (!*p)
{
// No closing found, highlight to end of file
- seg_end = text_len - 1;
+ seg_end = static_cast<S32>(wtext.size());
cur = base + seg_end;
- insertSegments(wtext, *seg_list, cur_delimiter, text_len, seg_start, seg_end, style, editor);
+ ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, cur_delimiter});
}
continue;
}
@@ -761,7 +746,7 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW
seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead();
}
- insertSegments(wtext, *seg_list, cur_delimiter, text_len, seg_start, seg_end, style, editor);
+ ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, cur_delimiter});
// Note: we don't increment cur, since the end of one delimited seg may be immediately
// followed by the start of another one.
continue;
@@ -794,13 +779,12 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW
S32 seg_end = seg_start + seg_len;
// First try to match the whole token (including dots for Lua namespaces)
- word_token_map_t::iterator map_iter = mWordTokenMap.find(WStringMapIndex(word_start, seg_len));
+ word_token_map_t::const_iterator map_iter = mWordTokenMap.find(WStringMapIndex(word_start, seg_len));
if (map_iter != mWordTokenMap.end())
{
// Found a match for the complete token (including any namespace)
- LLKeywordToken* cur_token = map_iter->second;
- insertSegments(wtext, *seg_list, cur_token, text_len, seg_start, seg_end, style, editor);
+ ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, map_iter->second});
}
else if (namespace_dots > 0 && mLuauLanguage)
{
@@ -815,8 +799,7 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW
if (map_iter != mWordTokenMap.end())
{
// Found a match for the namespace prefix, highlight just that part
- LLKeywordToken* cur_token = map_iter->second;
- insertSegments(wtext, *seg_list, cur_token, text_len, seg_start, seg_start + prefix_len, style, editor);
+ ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_start + prefix_len, map_iter->second});
// Now try to match the function part (after the dot)
const llwchar* func_part = last_dot + 1;
@@ -830,13 +813,7 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW
if (map_iter != mWordTokenMap.end())
{
// Found a match for the function part
- LLKeywordToken* cur_token = map_iter->second;
- insertSegments(wtext, *seg_list, cur_token, text_len, seg_start, seg_end, style, editor);
- }
- else
- {
- // No token found, continue without incrementing cur
- // since we already advanced it while collecting the word
+ ops.push_back({SegmentOp::OP_TOKEN, seg_start, seg_end, map_iter->second});
}
}
}
@@ -854,6 +831,84 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW
}
}
+bool LLKeywords::applySegmentOpsRange(std::vector<LLTextSegmentPtr> *seg_list,
+ const LLWString& wtext,
+ const segment_ops_t& ops,
+ size_t& op_index,
+ size_t max_ops,
+ LLTextEditor& editor,
+ LLStyleConstSP style)
+{
+ if (wtext.empty())
+ {
+ return true;
+ }
+
+ if (op_index == 0)
+ {
+ // Clear the segment list
+ seg_list->clear();
+ // Reserve capacity for segments based on an estimated average of 8 characters per segment.
+ seg_list->reserve(wtext.size() / AVERAGE_SEGMENT_LENGTH);
+
+ S32 text_len = static_cast<S32>(wtext.size()) + 1;
+ seg_list->push_back( new LLNormalTextSegment( style, 0, text_len, editor ) );
+ }
+
+ S32 text_len = static_cast<S32>(wtext.size()) + 1;
+ size_t end_index = op_index + max_ops;
+ if (end_index > ops.size())
+ {
+ end_index = ops.size();
+ }
+
+ for (; op_index < end_index; ++op_index)
+ {
+ const auto& op = ops[op_index];
+ if (op.type == SegmentOp::OP_LINE_BREAK)
+ {
+ LLTextSegmentPtr text_segment = new LLLineBreakTextSegment(style, op.start);
+ text_segment->setToken( 0 );
+ insertSegment( *seg_list, text_segment, text_len, style, editor);
+ }
+ else
+ {
+ insertSegments(wtext, *seg_list, op.token, text_len, op.start, op.end, style, editor);
+ }
+ }
+
+ return op_index >= ops.size();
+}
+
+void LLKeywords::applySegmentOps(std::vector<LLTextSegmentPtr> *seg_list,
+ const LLWString& wtext,
+ const segment_ops_t& ops,
+ LLTextEditor& editor,
+ LLStyleConstSP style)
+{
+ size_t op_index = 0;
+ applySegmentOpsRange(seg_list, wtext, ops, op_index, ops.size(), editor, style);
+}
+
+// Walk through a string, applying the rules specified by the keyword token list and
+// create a list of color segments.
+void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLWString& wtext, LLTextEditor& editor, LLStyleConstSP style)
+{
+ LL_RECORD_BLOCK_TIME(FTM_SYNTAX_COLORING);
+
+ if( wtext.empty() )
+ {
+ return;
+ }
+
+ static LLCachedControl<bool> sDisableSyntaxHighlighting(gSavedSettings, "ScriptEditorDisableSyntaxHighlight", false);
+ const bool disable_syntax_highlighting = sDisableSyntaxHighlighting;
+
+ segment_ops_t ops;
+ collectSegmentOps(ops, wtext, disable_syntax_highlighting);
+ applySegmentOps(seg_list, wtext, ops, editor, style);
+}
+
void LLKeywords::insertSegments(const LLWString& wtext, std::vector<LLTextSegmentPtr>& seg_list, LLKeywordToken* cur_token, S32 text_len, S32 seg_start, S32 seg_end, LLStyleConstSP style, LLTextEditor& editor )
{
std::string::size_type pos = wtext.find('\n',seg_start);
diff --git a/indra/llui/llkeywords.h b/indra/llui/llkeywords.h
index 3a02f0e68e..70fc2c20d5 100644
--- a/indra/llui/llkeywords.h
+++ b/indra/llui/llkeywords.h
@@ -36,6 +36,8 @@
#include <map>
#include <list>
#include <deque>
+#include <vector>
+#include <cstddef>
#include "llpointer.h"
class LLTextSegment;
@@ -124,6 +126,32 @@ public:
const LLWString& text,
class LLTextEditor& editor,
LLStyleConstSP style);
+ struct SegmentOp
+ {
+ enum EOpType
+ {
+ OP_LINE_BREAK,
+ OP_TOKEN
+ };
+ EOpType type;
+ S32 start;
+ S32 end;
+ LLKeywordToken* token;
+ };
+ typedef std::vector<SegmentOp> segment_ops_t;
+ void collectSegmentOps(segment_ops_t& ops, const LLWString& text, bool disable_syntax_highlighting) const;
+ void applySegmentOps(std::vector<LLTextSegmentPtr> *seg_list,
+ const LLWString& text,
+ const segment_ops_t& ops,
+ class LLTextEditor& editor,
+ LLStyleConstSP style);
+ bool applySegmentOpsRange(std::vector<LLTextSegmentPtr> *seg_list,
+ const LLWString& text,
+ const segment_ops_t& ops,
+ size_t& op_index,
+ size_t max_ops,
+ class LLTextEditor& editor,
+ LLStyleConstSP style);
void initialize(LLSD SyntaxXML, bool luau_language = false);
void processTokens();
diff --git a/indra/newview/llscripteditor.cpp b/indra/newview/llscripteditor.cpp
index d171d785a7..2a4c2b9d0b 100644
--- a/indra/newview/llscripteditor.cpp
+++ b/indra/newview/llscripteditor.cpp
@@ -31,6 +31,179 @@
#include "llsyntaxid.h"
#include "lllocalcliprect.h"
#include "llviewercontrol.h"
+#include "llworkerthread.h"
+#include <algorithm>
+#include <utility>
+
+static LLWorkerThread& getSyntaxWorkerThread()
+{
+ static LLWorkerThread* sThread = new LLWorkerThread("SyntaxParse", true);
+ return *sThread;
+}
+
+class LLScriptEditorSyntaxWorker final : public LLWorkerClass
+{
+public:
+ struct Request
+ {
+ LLWString text;
+ S32 text_generation = 0;
+ U32 keywords_generation = 0;
+ bool disable_highlight = false;
+ LLKeywords* keywords = nullptr;
+ };
+
+ struct Result
+ {
+ LLWString text;
+ LLKeywords::segment_ops_t ops;
+ S32 text_generation = 0;
+ U32 keywords_generation = 0;
+ bool disable_highlight = false;
+ LLKeywords* keywords = nullptr;
+ };
+
+ LLScriptEditorSyntaxWorker(LLWorkerThread* thread, LLScriptEditor* editor)
+ : LLWorkerClass(thread, "ScriptEditorSyntax"),
+ mEditor(editor),
+ mHasPending(false),
+ mDropResults(false)
+ {
+ }
+
+ void queueRequest(const Request& request)
+ {
+ bool should_add = false;
+ {
+ LLMutexLock lock(&mRequestMutex);
+ mPendingRequest = request;
+ mHasPending = true;
+ should_add = (!haveWork() && !isWorking());
+ }
+ if (should_add)
+ {
+ addWork(0);
+ }
+ }
+
+ void pump()
+ {
+ checkWork();
+ bool should_add = false;
+ {
+ LLMutexLock lock(&mRequestMutex);
+ should_add = (!haveWork() && !isWorking() && mHasPending);
+ }
+ if (should_add)
+ {
+ addWork(0);
+ }
+ }
+
+ void waitForIdle(bool drop_results)
+ {
+ mDropResults = drop_results;
+ {
+ LLMutexLock lock(&mRequestMutex);
+ mHasPending = false;
+ }
+ if (mRequestHandle != LLWorkerThread::nullHandle())
+ {
+ mWorkerThread->waitForResult(mRequestHandle, false);
+ checkWork();
+ }
+ mDropResults = false;
+ }
+
+ void shutdown()
+ {
+ mEditor = nullptr;
+ waitForIdle(true);
+ scheduleDelete();
+ }
+
+private:
+ void startWork(S32 param) override
+ {
+ LLMutexLock lock(&mRequestMutex);
+ if (!mHasPending)
+ {
+ return;
+ }
+ mActiveRequest = mPendingRequest;
+ mHasPending = false;
+ }
+
+ bool doWork(S32 param) override
+ {
+ Request request;
+ {
+ LLMutexLock lock(&mRequestMutex);
+ request = mActiveRequest;
+ }
+
+ Result result;
+ result.text = request.text;
+ result.text_generation = request.text_generation;
+ result.keywords_generation = request.keywords_generation;
+ result.disable_highlight = request.disable_highlight;
+ result.keywords = request.keywords;
+
+ if (request.keywords)
+ {
+ request.keywords->collectSegmentOps(result.ops, request.text, request.disable_highlight);
+ }
+
+ {
+ LLMutexLock lock(&mResultMutex);
+ mResult = std::move(result);
+ }
+ return true;
+ }
+
+ void endWork(S32 param, bool aborted) override
+ {
+ if (aborted || mDropResults || !mEditor)
+ {
+ return;
+ }
+
+ Result result;
+ {
+ LLMutexLock lock(&mResultMutex);
+ result = std::move(mResult);
+ }
+
+ if (mEditor->getTextGeneration() != result.text_generation)
+ {
+ return;
+ }
+ if (mEditor->getKeywordsGeneration() != result.keywords_generation)
+ {
+ return;
+ }
+ if (&mEditor->getKeywords() != result.keywords)
+ {
+ return;
+ }
+
+ mEditor->queueSyntaxApply(std::move(result.text),
+ std::move(result.ops),
+ result.text_generation,
+ result.keywords_generation,
+ result.disable_highlight,
+ result.keywords);
+ }
+
+ LLScriptEditor* mEditor;
+ LLMutex mRequestMutex;
+ LLMutex mResultMutex;
+ Request mPendingRequest;
+ Request mActiveRequest;
+ Result mResult;
+ bool mHasPending;
+ bool mDropResults;
+};
const S32 UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32;
@@ -46,7 +219,20 @@ LLScriptEditor::LLScriptEditor(const Params& p)
: LLTextEditor(p)
, mShowLineNumbers(p.show_line_numbers),
mUseDefaultFontSize(p.default_font_size),
- mLuauLanguage(false)
+ mLuauLanguage(false),
+ mKeywordsGeneration(0),
+ mLastQueuedTextGeneration(-1),
+ mLastQueuedKeywordsGeneration(0),
+ mLastQueuedDisableHighlight(false),
+ mLastQueuedKeywords(nullptr),
+ mSyntaxWorker(nullptr),
+ mSyntaxApplyState(SyntaxApplyState::Idle),
+ mPendingApplyOpIndex(0),
+ mPendingApplySegmentIndex(0),
+ mPendingApplyTextGeneration(-1),
+ mPendingApplyKeywordsGeneration(0),
+ mPendingApplyDisableHighlight(false),
+ mPendingApplyKeywords(nullptr)
{
if (mShowLineNumbers)
{
@@ -55,10 +241,22 @@ LLScriptEditor::LLScriptEditor(const Params& p)
}
}
+LLScriptEditor::~LLScriptEditor()
+{
+ if (mSyntaxWorker)
+ {
+ mSyntaxWorker->shutdown();
+ getSyntaxWorkerThread().update(0.f);
+ mSyntaxWorker = nullptr;
+ }
+}
+
bool LLScriptEditor::postBuild()
{
gSavedSettings.getControl("LSLFontSizeName")->getCommitSignal()->connect(boost::bind(&LLScriptEditor::onFontSizeChange, this));
- return LLTextEditor::postBuild();
+ bool result = LLTextEditor::postBuild();
+ setFont(getScriptFont());
+ return result;
}
void LLScriptEditor::draw()
@@ -154,41 +352,208 @@ void LLScriptEditor::initKeywords(bool luau_language)
void LLScriptEditor::loadKeywords()
{
LL_PROFILE_ZONE_SCOPED;
+ ensureSyntaxWorker();
+ mSyntaxWorker->waitForIdle(true);
+ resetPendingSyntaxApply();
getKeywords().processTokens();
+ ++mKeywordsGeneration;
- LLStyleConstSP style = new LLStyle(LLStyle::Params().font(getScriptFont()).color(mDefaultColor.get()));
-
- segment_vec_t segment_list;
- getKeywords().findSegments(&segment_list, getWText(), *this, style);
-
- mSegments.clear();
- segment_set_t::iterator insert_it = mSegments.begin();
- for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it)
+ LLKeywords::segment_ops_t ops;
+ const LLWString& text = getWText();
+ const llwchar* base = text.c_str();
+ for (const llwchar* cur = base; *cur; ++cur)
{
- insert_it = mSegments.insert(insert_it, *list_it);
+ if (*cur == '\n')
+ {
+ ops.push_back({LLKeywords::SegmentOp::OP_LINE_BREAK, (S32)(cur - base), 0, nullptr});
+ }
}
+ applySyntaxSegments(text, ops);
+ queueSyntaxParse();
}
void LLScriptEditor::updateSegments()
{
- if (mReflowIndex < S32_MAX && getKeywords().isLoaded() && mParseOnTheFly)
+ if (getKeywords().isLoaded() && mParseOnTheFly)
{
LL_PROFILE_ZONE_SCOPED;
+ ensureSyntaxWorker();
+ mSyntaxWorker->pump();
+ if (mReflowIndex < S32_MAX)
+ {
+ queueSyntaxParse();
+ }
+ processPendingSyntaxApply();
+ }
+
+ LLTextBase::updateSegments();
+}
+
+void LLScriptEditor::ensureSyntaxWorker()
+{
+ if (!mSyntaxWorker)
+ {
+ mSyntaxWorker = new LLScriptEditorSyntaxWorker(&getSyntaxWorkerThread(), this);
+ }
+}
+
+void LLScriptEditor::queueSyntaxParse()
+{
+ static LLCachedControl<bool> sDisableSyntaxHighlighting(gSavedSettings, "ScriptEditorDisableSyntaxHighlight", false);
+ const bool disable_syntax_highlighting = sDisableSyntaxHighlighting;
+
+ LLKeywords* keywords = &getKeywords();
+ const S32 text_generation = getTextGeneration();
+
+ if (mLastQueuedTextGeneration == text_generation
+ && mLastQueuedKeywordsGeneration == mKeywordsGeneration
+ && mLastQueuedDisableHighlight == disable_syntax_highlighting
+ && mLastQueuedKeywords == keywords)
+ {
+ return;
+ }
+
+ mLastQueuedTextGeneration = text_generation;
+ mLastQueuedKeywordsGeneration = mKeywordsGeneration;
+ mLastQueuedDisableHighlight = disable_syntax_highlighting;
+ mLastQueuedKeywords = keywords;
+
+ LLScriptEditorSyntaxWorker::Request request;
+ request.text = getWText();
+ request.text_generation = text_generation;
+ request.keywords_generation = mKeywordsGeneration;
+ request.disable_highlight = disable_syntax_highlighting;
+ request.keywords = keywords;
+ mSyntaxWorker->queueRequest(request);
+}
+
+void LLScriptEditor::queueSyntaxApply(LLWString text,
+ LLKeywords::segment_ops_t ops,
+ S32 text_generation,
+ U32 keywords_generation,
+ bool disable_highlight,
+ LLKeywords* keywords)
+{
+ if (text_generation != getTextGeneration()
+ || keywords_generation != mKeywordsGeneration
+ || keywords != &getKeywords())
+ {
+ return;
+ }
+
+ resetPendingSyntaxApply();
+
+ // Small updates can apply synchronously; larger ones use background slices.
+ constexpr size_t kImmediateOpsThreshold = 500;
+ if (ops.size() <= kImmediateOpsThreshold)
+ {
+ applySyntaxSegments(text, ops);
+ return;
+ }
+
+ mPendingApplyText = std::move(text);
+ mPendingApplyOps = std::move(ops);
+ mPendingApplyTextGeneration = text_generation;
+ mPendingApplyKeywordsGeneration = keywords_generation;
+ mPendingApplyDisableHighlight = disable_highlight;
+ mPendingApplyKeywords = keywords;
+ mPendingApplyOpIndex = 0;
+ mPendingApplySegmentIndex = 0;
+ mPendingApplySegments.clear();
+ mPendingApplySegmentSet.clear();
+ mPendingApplyStyle = new LLStyle(LLStyle::Params().font(getScriptFont()).color(mDefaultColor.get()));
+ mSyntaxApplyState = SyntaxApplyState::Building;
+}
+
+void LLScriptEditor::processPendingSyntaxApply()
+{
+ if (mSyntaxApplyState == SyntaxApplyState::Idle)
+ {
+ return;
+ }
- LLStyleConstSP style = new LLStyle(LLStyle::Params().font(getScriptFont()).color(mDefaultColor.get()));
+ static LLCachedControl<bool> sDisableSyntaxHighlighting(gSavedSettings, "ScriptEditorDisableSyntaxHighlight", false);
+ if (mPendingApplyTextGeneration != getTextGeneration()
+ || mPendingApplyKeywordsGeneration != mKeywordsGeneration
+ || mPendingApplyKeywords != &getKeywords()
+ || mPendingApplyDisableHighlight != sDisableSyntaxHighlighting)
+ {
+ resetPendingSyntaxApply();
+ return;
+ }
- // HACK: No non-ascii keywords for now
- segment_vec_t segment_list;
- getKeywords().findSegments(&segment_list, getWText(), *this, style);
+ // Slice sizes control UI budget while applying large highlight updates.
+ constexpr size_t kOpsPerSlice = 250;
+ constexpr size_t kSegmentsPerSlice = 250;
- clearSegments();
- for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it)
+ if (mSyntaxApplyState == SyntaxApplyState::Building)
+ {
+ if (mPendingApplyStyle.isNull())
{
- insertSegment(*list_it);
+ mPendingApplyStyle = new LLStyle(LLStyle::Params().font(getScriptFont()).color(mDefaultColor.get()));
}
+ bool done = getKeywords().applySegmentOpsRange(&mPendingApplySegments,
+ mPendingApplyText,
+ mPendingApplyOps,
+ mPendingApplyOpIndex,
+ kOpsPerSlice,
+ *this,
+ mPendingApplyStyle);
+ if (done)
+ {
+ mSyntaxApplyState = SyntaxApplyState::Inserting;
+ }
+ return;
}
- LLTextBase::updateSegments();
+ size_t end_index = std::min(mPendingApplySegmentIndex + kSegmentsPerSlice, mPendingApplySegments.size());
+ for (; mPendingApplySegmentIndex < end_index; ++mPendingApplySegmentIndex)
+ {
+ LLTextSegmentPtr segment = mPendingApplySegments[mPendingApplySegmentIndex];
+ segment->linkToDocument(this);
+ mPendingApplySegmentSet.insert(segment);
+ }
+
+ if (mPendingApplySegmentIndex >= mPendingApplySegments.size())
+ {
+ S32 saved_scroll_index = mScrollIndex;
+ segment_set_t old_segments;
+ old_segments.swap(mSegments);
+ mSegments.swap(mPendingApplySegmentSet);
+ mScrollIndex = saved_scroll_index;
+ needsReflow(0);
+ resetPendingSyntaxApply();
+ }
+}
+
+void LLScriptEditor::resetPendingSyntaxApply()
+{
+ mSyntaxApplyState = SyntaxApplyState::Idle;
+ mPendingApplyText.clear();
+ mPendingApplyOps.clear();
+ mPendingApplySegments.clear();
+ mPendingApplySegmentSet.clear();
+ mPendingApplyOpIndex = 0;
+ mPendingApplySegmentIndex = 0;
+ mPendingApplyStyle = LLStyleConstSP();
+ mPendingApplyTextGeneration = -1;
+ mPendingApplyKeywordsGeneration = 0;
+ mPendingApplyDisableHighlight = false;
+ mPendingApplyKeywords = nullptr;
+}
+
+void LLScriptEditor::applySyntaxSegments(const LLWString& text, const LLKeywords::segment_ops_t& ops)
+{
+ LLStyleConstSP style = new LLStyle(LLStyle::Params().font(getScriptFont()).color(mDefaultColor.get()));
+
+ segment_vec_t segment_list;
+ getKeywords().applySegmentOps(&segment_list, text, ops, *this, style);
+
+ clearSegments();
+ for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it)
+ {
+ insertSegment(*list_it);
+ }
}
void LLScriptEditor::clearSegments()
@@ -260,6 +625,7 @@ void LLScriptEditor::onFontSizeChange()
{
if (!mUseDefaultFontSize)
{
+ setFont(getScriptFont());
needsReflow();
}
}
diff --git a/indra/newview/llscripteditor.h b/indra/newview/llscripteditor.h
index 2d98110f75..dcbfdf3085 100644
--- a/indra/newview/llscripteditor.h
+++ b/indra/newview/llscripteditor.h
@@ -29,6 +29,7 @@
#define LL_SCRIPTEDITOR_H
#include "lltexteditor.h"
+#include <cstddef>
class LLScriptEditor : public LLTextEditor
{
@@ -41,7 +42,7 @@ public:
Params();
};
- ~LLScriptEditor() override {};
+ ~LLScriptEditor() override;
// LLView override
void draw() override;
@@ -55,6 +56,8 @@ public:
LLKeywords& getKeywords();
bool getIsLuauLanguage() { return mLuauLanguage; }
void setLuauLanguage(bool luau_language) { mLuauLanguage = luau_language; }
+ U32 getKeywordsGeneration() const { return mKeywordsGeneration; }
+ void applySyntaxSegments(const LLWString& text, const LLKeywords::segment_ops_t& ops);
static std::string getScriptFontSize();
LLFontGL* getScriptFont();
@@ -62,12 +65,29 @@ public:
protected:
friend class LLUICtrlFactory;
+ friend class LLScriptEditorSyntaxWorker;
LLScriptEditor(const Params& p);
private:
+ enum class SyntaxApplyState
+ {
+ Idle,
+ Building,
+ Inserting
+ };
void drawLineNumbers();
void updateSegments() override;
void drawSelectionBackground() override;
+ void ensureSyntaxWorker();
+ void queueSyntaxParse();
+ void queueSyntaxApply(LLWString text,
+ LLKeywords::segment_ops_t ops,
+ S32 text_generation,
+ U32 keywords_generation,
+ bool disable_highlight,
+ LLKeywords* keywords);
+ void processPendingSyntaxApply();
+ void resetPendingSyntaxApply();
LLKeywords mKeywordsLua;
LLKeywords mKeywordsLSL;
@@ -75,6 +95,24 @@ private:
bool mShowLineNumbers;
bool mUseDefaultFontSize;
+ U32 mKeywordsGeneration;
+ S32 mLastQueuedTextGeneration;
+ U32 mLastQueuedKeywordsGeneration;
+ bool mLastQueuedDisableHighlight;
+ LLKeywords* mLastQueuedKeywords;
+ class LLScriptEditorSyntaxWorker* mSyntaxWorker;
+ SyntaxApplyState mSyntaxApplyState;
+ LLWString mPendingApplyText;
+ LLKeywords::segment_ops_t mPendingApplyOps;
+ segment_vec_t mPendingApplySegments;
+ segment_set_t mPendingApplySegmentSet;
+ size_t mPendingApplyOpIndex;
+ size_t mPendingApplySegmentIndex;
+ LLStyleConstSP mPendingApplyStyle;
+ S32 mPendingApplyTextGeneration;
+ U32 mPendingApplyKeywordsGeneration;
+ bool mPendingApplyDisableHighlight;
+ LLKeywords* mPendingApplyKeywords;
};
#endif // LL_SCRIPTEDITOR_H