Ode Script Editor: A simple embedded script editor entirely in Roblox

It was just how Ode was designed, and changing it would take too much time at the moment.

You inspired me to make my own!
image

1 Like

Version 1.1.0

Thank you @pinehappi for contributing.

This version implements the :Destroy() method and .AutoWrapEnabled field. The Creator Store version has not yet been updated, but the rbxm file has been uploaded to GitHub. Below are the release notes generated by GitHub.


What’s Changed

  • Fix y-drift of rich text overlays and line numbers by @pinehappi in #1
  • Implement destroy method by @SpaceDice9 in #3
  • Improve lexer accuracy by @SpaceDice9 in #4
  • Fix performance drop when scrolling with large source data by @SpaceDice9 in #5
  • Fix CodeField not updating correctly when resizing container frame by @SpaceDice9 in #6
  • Implement autowrapping for some characters by @SpaceDice9 in #7

New Contributors

Full Changelog: Commits ¡ SpaceDice9/OdeScriptEditor ¡ GitHub

2 Likes

I’m having an issue with displaying the text color

Can you show a screenshot and error logs if any? Also make sure to give me a place file where I can recreate the issue.

There is no error actually. I just took the model as it was from the model on your github and added it to a baseplate.

The one listed as a release is a little outdated compared to the git repo itself. Try this one and see if the problem persists.

OdeScriptEditor.rbxm (33.0 KB)

Hey, I just tried it out and it still doesn’t seem to work.

This is so cool, it makes it 10 times easier than writing quick scripts from the dev console.

Tysm, this saved me so much time, there’s 1 issues with it is that it’s not really optimized since it has to modify the entire rich text everytime

That’s strange. Works perfectly for me. Are you initializing Ode correctly? This is how I do it.
Screenshot 2024-09-22 114327

local Ode = require(game.ReplicatedStorage.OdeScriptEditor)
local frame: Frame = script.Parent.Frame

Ode.Embed(frame)

Also, if you still have the place file (the .rbxl one) please send it to me so I can help you diagnose the issue :slight_smile:

Alright, I just tried initializing it like you stated above but nothing changes. Here is the .rbxl file
Editor.rbxl (89.0 KB)

can be used in game? i want to make a runtime dev only executor (not for hacks just for debugging) in live serve4s

I see the problem now. The initializer script needs to be a local script to run properly. That fixes the issue.

Alright, thank you for the help.

Sure! Keep in mind however that this is just an editor and has no execution functionality. That is something you would have to implement yourself. Also, I’ve seen that older versions might not work properly under a fully published game so make sure to grab the latest one using Rojo if possible (this version also has a slightly different api, :LoadScriptAsync and :LoadStringAsync are replaced with :SetScriptAsync and :SetStringAsync. If you can’t, I can provide the file here:
OdeScriptEditor.rbxm (24.4 KB)

1 Like

I was having problems with deleting text when the text went over the screen. To fix it I made this adjustment to the GetTextBoxScrolling module script

local TextService = game:GetService(“TextService”)
– Only works when font is monospaced and x size is exactly C * width of character + 1 (6 * 7 + 1 = 43)

function getMultiLinePositions(text)
local multilines = string.split(text, “\n”)
local multilinePositions = {}
local cursor = 1
local maxLineLength = 0
local maxLineIndex = -1

for i, line in multilines do
	local lineLength = string.len(line)
	
	table.insert(multilinePositions, {cursor, cursor + lineLength})

	if lineLength > maxLineLength then
		maxLineLength = lineLength
		maxLineIndex = i
	end

	cursor += lineLength + 1
end

return multilines, multilinePositions, maxLineLength, maxLineIndex

end

function getLinePosition(multilinePositions, position)
for i, multilinePosition in multilinePositions do
if (position >= multilinePosition[1] and position <= multilinePosition[2]) then
return i, position - multilinePosition[1] + 1
end
end

return -1, -1

end

function getLengthOfLine(text, textLabel: TextBox)
local absoluteSize = TextService:GetTextSize(
text,
textLabel.TextSize,
textLabel.Font,
Vector2.new(math.huge, math.huge)
)

return absoluteSize.X

end

function getShiftAmount(lower, upper, maxlength, windowWidth, x)
–print(string.format(“LineLength: %d MaxLength: %d, Width: %d, Lower: %d, Upper: %d”, x, maxlength or -1, windowWidth, lower, upper))

if maxlength and maxlength < upper then
	return maxlength - upper
elseif x < lower then
	return x - lower
elseif x > upper then
	return x - upper
else
	return 0
end

end

local GetTextBoxScrolling = {}

function GetTextBoxScrolling.UpdateShift(textLabel: TextBox, cursorPosition, shift)
if cursorPosition == -1 then
return 0
end
– print(“Update Shift”)
local labelWindow = textLabel.AbsoluteSize.X - 1

local multilines, multilinePositions, maxLinePosition, maxLineIndex = getMultiLinePositions(textLabel.Text)
local linePosition, positionLength = getLinePosition(multilinePositions, cursorPosition)
local line = multilines[linePosition]
local stringUntilCursor = string.sub(line, 0, positionLength - 1)
local lineLength = getLengthOfLine(stringUntilCursor, textLabel)



local maxLineLength = (maxLineIndex > 0) and getLengthOfLine(multilines[maxLineIndex], textLabel)

local relativeShift = getShiftAmount(shift, shift + labelWindow, maxLineLength, labelWindow, lineLength)

shift = math.max(0, shift + relativeShift)
--print(shift, relativeShift)

return shift

end

return GetTextBoxScrolling

Can you submit your changes on GitHub? The code snippet you sent here is badly formatted. When writing code snippets on the DevForum please use ``` to denote code.

I don’t know how to submit it on GitHub, so I’ll submit it here with properly formatted code.
The issue was when the line you’re editing goes beyond the horizontal window space, deleting from that line would desync the underlying text from the textbox with the rich overlay text.

What I changed:

  1. I calculate the max horizontal length of the entire text and used it in getShiftAmount() to properly sync it

Ignore the maxShift return field in UpdateShift(), it was part with a horizontal scroll bar I added. The horizontal scroll bar works but it desyncs the text again (because the TextBox only scrolls horizontally when focused). I propose removing the CodeField textbox entirely and making your own input events, fake cursor, and text selection functionality. The base CodeField functionality doesnt work well with new features like horizontal scroll.

GetTextBoxScrolling.lua

local TextService = game:GetService("TextService")
-- Only works when font is monospaced and x size is exactly C * width of character + 1 (6 * 7 + 1 = 43)

function getMultiLinePositions(text)
	local multilines = string.split(text, "\n")
	local multilinePositions = {}
	local cursor = 1
	local maxLineLength = 0
	local maxLineIndex = -1

	for i, line in multilines do
		local lineLength = string.len(line)
		
		table.insert(multilinePositions, {cursor, cursor + lineLength})

		if lineLength > maxLineLength then
			maxLineLength = lineLength
			maxLineIndex = i
		end

		cursor += lineLength + 1
	end

	return multilines, multilinePositions, maxLineLength, maxLineIndex
end

function getLinePosition(multilinePositions, position)
	for i, multilinePosition in multilinePositions do
		if (position >= multilinePosition[1] and position <= multilinePosition[2]) then
			return i, position - multilinePosition[1] + 1
		end
	end

	return -1, -1
end

function getLengthOfLine(text, textLabel: TextBox)
	local absoluteSize = TextService:GetTextSize(
		text,
		textLabel.TextSize,
		textLabel.Font,
		Vector2.new(math.huge, math.huge)
	)

	return absoluteSize.X
end

function getShiftAmount(lower, upper, maxlength, windowWidth, x)
	--print(string.format("LineLength: %d MaxLength: %d, Width: %d, Lower: %d, Upper: %d", x, maxlength or -1, windowWidth, lower, upper))
	
	if maxlength and maxlength < upper then
		return maxlength - upper
	elseif x < lower then
		return x - lower
	elseif x > upper then
		return x - upper
	else
		return 0
	end
end

local GetTextBoxScrolling = {}


function GetTextBoxScrolling.UpdateShift(textLabel: TextBox, cursorPosition, shift)
	if cursorPosition == -1 then
		return shift
	end
--	print("Update Shift")
	local labelWindow = textLabel.AbsoluteSize.X - 1

	local multilines, multilinePositions, maxLinePosition, maxLineIndex = getMultiLinePositions(textLabel.Text)
	local linePosition, positionLength = getLinePosition(multilinePositions, cursorPosition)
	local line = multilines[linePosition]
	local stringUntilCursor = string.sub(line, 0, positionLength - 1)
	local lineLength = getLengthOfLine(stringUntilCursor, textLabel)
	
	
	
	local maxLineLength = (maxLineIndex > 0) and getLengthOfLine(multilines[maxLineIndex], textLabel)
	
	local relativeShift = getShiftAmount(shift, shift + labelWindow, maxLineLength, labelWindow, lineLength)
	local maxShift = if maxLineLength then math.max(0, maxLineLength - labelWindow) else 0

	shift = math.max(0, shift + relativeShift)
	--print(shift, relativeShift)

	return shift, maxShift
end

return GetTextBoxScrolling
1 Like