summaryrefslogtreecommitdiff
path: root/indra/newview/llpreviewscript.cpp
diff options
context:
space:
mode:
authorRider Linden <rider@lindenlab.com>2025-10-10 12:28:41 -0700
committerGitHub <noreply@github.com>2025-10-10 12:28:41 -0700
commitdd9ea09d50ef5a4992d2ae29d2970ce05998bad0 (patch)
treed4500ea28b20dcb0aa274b3bbc0ad10585634193 /indra/newview/llpreviewscript.cpp
parent0b7bc35131ce4a526c1e696904c4e8332e1b0fe9 (diff)
parentc21fda0b79c2e18c0dccf9e88524a716b57b94bf (diff)
Merge pull request #4599 from secondlife/rider/vscode_integration
Websockets Server implementation for script editor integration with VSCode or other external editors. Full implementation of JSON-RPC server bound to external code editing.
Diffstat (limited to 'indra/newview/llpreviewscript.cpp')
-rw-r--r--indra/newview/llpreviewscript.cpp148
1 files changed, 113 insertions, 35 deletions
diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp
index 69f5812853..b3e53df6d8 100644
--- a/indra/newview/llpreviewscript.cpp
+++ b/indra/newview/llpreviewscript.cpp
@@ -65,22 +65,12 @@
#include "llviewerobject.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
-#include "llkeyboard.h"
-#include "llscrollcontainer.h"
-#include "llcheckboxctrl.h"
#include "llscripteditor.h"
-#include "llselectmgr.h"
-#include "lltooldraganddrop.h"
-#include "llscrolllistctrl.h"
#include "lltextbox.h"
-#include "llslider.h"
-#include "lldir.h"
-#include "llcombobox.h"
#include "llviewerstats.h"
#include "llviewerwindow.h"
#include "lluictrlfactory.h"
#include "llmediactrl.h"
-#include "lluictrlfactory.h"
#include "lltrans.h"
#include "llviewercontrol.h"
#include "llappviewer.h"
@@ -91,6 +81,8 @@
#include "lltoggleablemenu.h"
#include "llmenubutton.h"
#include "llinventoryfunctions.h"
+#include "llwebsocketmgr.h"
+#include "llscripteditorws.h"
#include <regex>
const std::string HELP_LSL_PORTAL_TOPIC = "LSL_Portal";
@@ -1121,19 +1113,21 @@ void LLScriptEdCore::doSave( bool close_after_save )
void LLScriptEdCore::openInExternalEditor()
{
- if (mContainer->mLiveFile)
- {
- // If already open in an external editor, just return
- return;
- }
+ //if (mContainer->mLiveFile)
+ //{
+ // // If already open in an external editor, just return
+ // return;
+ //}
// Generate a suitable filename
std::string script_name = mScriptName;
- std::string forbidden_chars = "<>:\"\\/|?*";
- for (std::string::iterator c = forbidden_chars.begin(); c != forbidden_chars.end(); c++)
- {
- script_name.erase(std::remove(script_name.begin(), script_name.end(), *c), script_name.end());
- }
+
+ static const std::set<char> forbidden_chars{ '<', '>', ':', '"', '\\', '/', '|', '?', '*' };
+ script_name.erase(
+ std::remove_if(script_name.begin(), script_name.end(), [](char c) {
+ return forbidden_chars.contains(c);
+ }), script_name.end());
+
std::string filename = mContainer->getTmpFileName(script_name);
// Save the script to a temporary file.
@@ -1147,9 +1141,18 @@ void LLScriptEdCore::openInExternalEditor()
writeToFile(filename);
}
+ if (mContainer->mLiveFile && mContainer->mLiveFile->filename() != filename)
+ { // The name may have changed if we changed the type of scipt being edited.
+ delete mContainer->mLiveFile;
+ mContainer->mLiveFile = NULL;
+ }
// Start watching file changes.
- mContainer->mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLScriptEdContainer::onExternalChange, mContainer, _1));
- mContainer->mLiveFile->addToEventTimer();
+ if (!mContainer->mLiveFile)
+ {
+ mContainer->mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLScriptEdContainer::onExternalChange, mContainer, _1));
+ mContainer->mLiveFile->addToEventTimer();
+ }
+ mContainer->startWebsocketServer();
// Open it in external editor.
{
@@ -1505,8 +1508,6 @@ void LLScriptEdCore::setAssociatedExperience( const LLUUID& experience_id )
mAssociatedExperience = experience_id;
}
-
-
void LLLiveLSLEditor::requestExperiences()
{
if (!getIsModifiable())
@@ -1558,20 +1559,16 @@ LLScriptEdContainer::~LLScriptEdContainer()
delete mLiveLogFile;
mLiveLogFile = nullptr;
+
+ if (!mWebSocketServer.expired())
+ {
+ unsubscribeScript();
+ }
}
-std::string LLScriptEdContainer::getTmpFileName(const std::string& script_name)
+std::string LLScriptEdContainer::getTmpFileName(const std::string& script_name) const
{
- // Take script inventory item id (within the object inventory)
- // to consideration so that it's possible to edit multiple scripts
- // in the same object inventory simultaneously (STORM-781).
- std::string script_id = mObjectUUID.asString() + "_" + mItemUUID.asString();
-
- // Use MD5 sum to make the file name shorter and not exceed maximum path length.
- char script_id_hash_str[33]; /* Flawfinder: ignore */
- LLMD5 script_id_hash((const U8 *)script_id.c_str());
- script_id_hash.hex_digest(script_id_hash_str);
-
+ std::string script_id_hash_str(getUniqueHash());
std::string script_extension = mScriptEd->mEditor->getIsLuauLanguage() ? ".luau" : ".lsl";
if (script_name.empty())
@@ -1584,6 +1581,21 @@ std::string LLScriptEdContainer::getTmpFileName(const std::string& script_name)
}
}
+std::string LLScriptEdContainer::getUniqueHash() const
+{
+ // Take script inventory item id (within the object inventory)
+ // to consideration so that it's possible to edit multiple scripts
+ // in the same object inventory simultaneously (STORM-781).
+ std::string script_id = mObjectUUID.asString() + "_" + mItemUUID.asString();
+
+ // Use MD5 sum to make the file name shorter and not exceed maximum path length.
+ char script_id_hash_str[33]; /* Flawfinder: ignore */
+ LLMD5 script_id_hash((const U8*)script_id.c_str());
+ script_id_hash.hex_digest(script_id_hash_str);
+
+ return std::string(script_id_hash_str);
+}
+
std::string LLScriptEdContainer::getErrorLogFileName(const std::string& script_path)
{
if (script_path.empty())
@@ -1665,6 +1677,63 @@ bool LLScriptEdContainer::handleKeyHere(KEY key, MASK mask)
return true;
}
+void LLScriptEdContainer::startWebsocketServer()
+{
+ if (gSavedSettings.getBOOL("ExternalWebsocketSyncEnable"))
+ {
+ // Attempt to find an existing server
+ LLWebsocketMgr& wsmgr = LLWebsocketMgr::instance();
+ LLScriptEditorWSServer::ptr_t server =
+ std::static_pointer_cast<LLScriptEditorWSServer>(
+ wsmgr.findServerByName(LLScriptEditorWSServer::DEFAULT_SERVER_NAME));
+
+ if (!server)
+ { // We couldn't find one, so create it
+ U16 server_port = static_cast<U16>(gSavedSettings.getS32("ExternalWebsocketSyncPort"));
+ bool server_localhost = gSavedSettings.getBOOL("ExternalWebsocketSyncLocal");
+ server = std::make_shared<LLScriptEditorWSServer>(LLScriptEditorWSServer::DEFAULT_SERVER_NAME, server_port, server_localhost);
+ wsmgr.addServer(server);
+ }
+
+ bool is_running = server->isRunning();
+ if (!is_running)
+ { // Server isn't running, so start it
+ is_running = wsmgr.startServer(LLScriptEditorWSServer::DEFAULT_SERVER_NAME);
+ }
+
+ if (!is_running && !server->isRunning())
+ { // Failed to start the server
+ LL_WARNS() << "Failed to start script editor websocket server" << LL_ENDL;
+ return;
+ }
+
+ std::string script_id_hash_str(getUniqueHash());
+ server->subscribeScriptEditor(mObjectUUID, mItemUUID, mScriptEd->mScriptName, getHandle(), script_id_hash_str);
+ mWebSocketServer = server;
+ }
+}
+
+void LLScriptEdContainer::unsubscribeScript()
+{
+ auto server = mWebSocketServer.lock();
+ if (server)
+ {
+ std::string script_id_hash_str(getUniqueHash());
+ server->sendUnsubscribeScriptEditor(script_id_hash_str);
+ server->unsubscribeEditor(script_id_hash_str);
+ }
+}
+
+void LLScriptEdContainer::sendCompileResults(LLSD& params)
+{
+ auto server = mWebSocketServer.lock();
+ if (server)
+ {
+ std::string script_id_hash_str(getUniqueHash());
+ server->sendCompileResults(script_id_hash_str, params);
+ }
+}
+
/// ---------------------------------------------------------------------------
/// LLPreviewLSL
/// ---------------------------------------------------------------------------
@@ -1902,6 +1971,7 @@ void LLPreviewLSL::finishedLSLUpload(LLUUID itemId, LLSD response)
{
preview->callbackLSLCompileFailed(response["errors"]);
}
+ preview->sendCompileResults(response);
}
}
@@ -1925,6 +1995,12 @@ bool LLPreviewLSL::failedLSLUpload(LLUUID itemId, LLUUID taskId, LLSD response,
LLSD errors;
errors.append(LLTrans::getString("UploadFailed") + reason);
preview->callbackLSLCompileFailed(errors);
+
+ LLSD message;
+ message["compiled"] = false;
+ message["errors"] = errors;
+ preview->sendCompileResults(message);
+
return true;
}
@@ -2459,6 +2535,8 @@ void LLLiveLSLEditor::finishLSLUpload(LLUUID itemId, LLUUID taskId, LLUUID newAs
{
preview->callbackLSLCompileFailed(response["errors"]);
}
+ response["is_running"] = isRunning;
+ preview->sendCompileResults(response);
}
}