1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-29 16:47:56 +00:00

Merge branch 'mc-1.20.x' into mc-1.21.x

This commit is contained in:
Jonathan Coates
2025-06-28 11:21:17 +01:00
24 changed files with 939 additions and 717 deletions

View File

@@ -12,7 +12,7 @@ neogradle.subsystems.conventions.runs.enabled=false
# Mod properties # Mod properties
isUnstable=true isUnstable=true
modVersion=1.115.1 modVersion=1.116.0
# Minecraft properties: We want to configure this here so we can read it in settings.gradle # Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.21.1 mcVersion=1.21.1

View File

@@ -69,7 +69,7 @@ ideaExt = "1.1.7"
illuaminate = "0.1.0-83-g1131f68" illuaminate = "0.1.0-83-g1131f68"
lwjgl = "3.3.3" lwjgl = "3.3.3"
minotaur = "2.8.7" minotaur = "2.8.7"
modDevGradle = "2.0.78" modDevGradle = "2.0.95"
nullAway = "0.12.7" nullAway = "0.12.7"
shadow = "8.3.1" shadow = "8.3.1"
spotless = "7.0.2" spotless = "7.0.2"

676
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,10 +13,10 @@
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-typescript": "^12.0.0", "@rollup/plugin-typescript": "^12.0.0 && <12.1.3",
"@rollup/plugin-url": "^8.0.1", "@rollup/plugin-url": "^8.0.1",
"@swc/core": "^1.3.92", "@swc/core": "^1.3.92",
"@types/node": "^22.0.0", "@types/node": "^24.0.0",
"lightningcss": "^1.22.0", "lightningcss": "^1.22.0",
"preact-render-to-string": "^6.2.1", "preact-render-to-string": "^6.2.1",
"rehype": "^13.0.0", "rehype": "^13.0.0",

View File

@@ -205,8 +205,10 @@
"item.computercraft.treasure_disk": "Disketa", "item.computercraft.treasure_disk": "Disketa",
"itemGroup.computercraft": "ComputerCraft", "itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.computer": "Počítače", "tag.item.computercraft.computer": "Počítače",
"tag.item.computercraft.disks": "Disky",
"tag.item.computercraft.dyeable": "Obarvitelné předměty", "tag.item.computercraft.dyeable": "Obarvitelné předměty",
"tag.item.computercraft.monitor": "Monitory", "tag.item.computercraft.monitor": "Monitory",
"tag.item.computercraft.pocket_computers": "Kapesní počítače",
"tag.item.computercraft.turtle": "Roboti", "tag.item.computercraft.turtle": "Roboti",
"tag.item.computercraft.turtle_can_place": "Roboty-umístitelné předměty", "tag.item.computercraft.turtle_can_place": "Roboty-umístitelné předměty",
"tag.item.computercraft.wired_modem": "Drátové modemy", "tag.item.computercraft.wired_modem": "Drátové modemy",

View File

@@ -177,6 +177,7 @@
"item.computercraft.treasure_disk": "Diskette", "item.computercraft.treasure_disk": "Diskette",
"itemGroup.computercraft": "ComputerCraft", "itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.computer": "Computer", "tag.item.computercraft.computer": "Computer",
"tag.item.computercraft.dyeable": "Färbbare Elemente",
"tag.item.computercraft.monitor": "Monitore", "tag.item.computercraft.monitor": "Monitore",
"tag.item.computercraft.turtle": "Turtles", "tag.item.computercraft.turtle": "Turtles",
"tag.item.computercraft.wired_modem": "Verkabelte Modems", "tag.item.computercraft.wired_modem": "Verkabelte Modems",

View File

@@ -205,8 +205,10 @@
"item.computercraft.treasure_disk": "Disco Floppy", "item.computercraft.treasure_disk": "Disco Floppy",
"itemGroup.computercraft": "ComputerCraft", "itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.computer": "Computer", "tag.item.computercraft.computer": "Computer",
"tag.item.computercraft.dyeable": "Oggetti colorabili",
"tag.item.computercraft.monitor": "Monitor", "tag.item.computercraft.monitor": "Monitor",
"tag.item.computercraft.turtle": "Tartarughe", "tag.item.computercraft.turtle": "Tartarughe",
"tag.item.computercraft.turtle_can_place": "Oggetti piazzabili da tartarughe",
"tag.item.computercraft.wired_modem": "Modem cablati", "tag.item.computercraft.wired_modem": "Modem cablati",
"tracking_field.computercraft.avg": "%s (media)", "tracking_field.computercraft.avg": "%s (media)",
"tracking_field.computercraft.computer_tasks.name": "Attività", "tracking_field.computercraft.computer_tasks.name": "Attività",

View File

@@ -205,8 +205,10 @@
"item.computercraft.treasure_disk": "フロッピーディスク", "item.computercraft.treasure_disk": "フロッピーディスク",
"itemGroup.computercraft": "ComputerCraft", "itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.computer": "コンピューター", "tag.item.computercraft.computer": "コンピューター",
"tag.item.computercraft.disks": "ディスク",
"tag.item.computercraft.dyeable": "染色可能なアイテム", "tag.item.computercraft.dyeable": "染色可能なアイテム",
"tag.item.computercraft.monitor": "モニター", "tag.item.computercraft.monitor": "モニター",
"tag.item.computercraft.pocket_computers": "ポケットコンピューター",
"tag.item.computercraft.turtle": "タートル", "tag.item.computercraft.turtle": "タートル",
"tag.item.computercraft.turtle_can_place": "タートルが設置可能なアイテム", "tag.item.computercraft.turtle_can_place": "タートルが設置可能なアイテム",
"tag.item.computercraft.wired_modem": "有線モデム", "tag.item.computercraft.wired_modem": "有線モデム",

View File

@@ -92,6 +92,7 @@
"item.computercraft.printed_pages": "Wydrukowane Strony", "item.computercraft.printed_pages": "Wydrukowane Strony",
"item.computercraft.treasure_disk": "Dyskietka", "item.computercraft.treasure_disk": "Dyskietka",
"itemGroup.computercraft": "ComputerCraft", "itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.turtle_can_place": "Przedmioty stawialne przez Żółwia",
"upgrade.computercraft.speaker.adjective": "Hałaśliwy", "upgrade.computercraft.speaker.adjective": "Hałaśliwy",
"upgrade.computercraft.wireless_modem_advanced.adjective": "Enderowy", "upgrade.computercraft.wireless_modem_advanced.adjective": "Enderowy",
"upgrade.computercraft.wireless_modem_normal.adjective": "Bezprzewodowy", "upgrade.computercraft.wireless_modem_normal.adjective": "Bezprzewodowy",

View File

@@ -205,8 +205,10 @@
"item.computercraft.treasure_disk": "Disket", "item.computercraft.treasure_disk": "Disket",
"itemGroup.computercraft": "ComputerCraft", "itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.computer": "Bilgisayarlar", "tag.item.computercraft.computer": "Bilgisayarlar",
"tag.item.computercraft.disks": "Diskler",
"tag.item.computercraft.dyeable": "Boyanabilir eşyalar", "tag.item.computercraft.dyeable": "Boyanabilir eşyalar",
"tag.item.computercraft.monitor": "Monitörler", "tag.item.computercraft.monitor": "Monitörler",
"tag.item.computercraft.pocket_computers": "Cep Bilgisayarları",
"tag.item.computercraft.turtle": "Turtlelar", "tag.item.computercraft.turtle": "Turtlelar",
"tag.item.computercraft.turtle_can_place": "Kaplumbağa-yerleştirilebilir eşyalar", "tag.item.computercraft.turtle_can_place": "Kaplumbağa-yerleştirilebilir eşyalar",
"tag.item.computercraft.wired_modem": "Kablolu modemler", "tag.item.computercraft.wired_modem": "Kablolu modemler",

View File

@@ -205,8 +205,10 @@
"item.computercraft.treasure_disk": "软盘", "item.computercraft.treasure_disk": "软盘",
"itemGroup.computercraft": "ComputerCraft", "itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.computer": "计算机", "tag.item.computercraft.computer": "计算机",
"tag.item.computercraft.disks": "磁盘",
"tag.item.computercraft.dyeable": "可染色物品", "tag.item.computercraft.dyeable": "可染色物品",
"tag.item.computercraft.monitor": "监视器", "tag.item.computercraft.monitor": "监视器",
"tag.item.computercraft.pocket_computers": "便携式计算机",
"tag.item.computercraft.turtle": "海龟", "tag.item.computercraft.turtle": "海龟",
"tag.item.computercraft.turtle_can_place": "可放置海龟物品", "tag.item.computercraft.turtle_can_place": "可放置海龟物品",
"tag.item.computercraft.wired_modem": "有线调制解调器", "tag.item.computercraft.wired_modem": "有线调制解调器",

View File

@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.gametest
import dan200.computercraft.core.util.Colour
import dan200.computercraft.gametest.api.craftItem
import dan200.computercraft.gametest.api.immediate
import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.shared.util.DataComponentUtil
import dan200.computercraft.test.shared.ItemStackMatcher.isStack
import net.minecraft.core.component.DataComponents
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.component.DyedItemColor
import org.hamcrest.MatcherAssert.assertThat
class Disk_Test {
/**
* Ensure disks
*/
@GameTest(template = "default")
fun Can_craft_disk(helper: GameTestHelper) = helper.immediate {
assertThat(
"Disk without dye",
helper.craftItem(ItemStack(Items.REDSTONE), ItemStack(Items.PAPER)),
isStack(
DataComponentUtil.createStack(
ModRegistry.Items.DISK.get(),
DataComponents.DYED_COLOR,
DyedItemColor(Colour.BLUE.hex, false),
),
),
)
assertThat(
"Disk with dye",
helper.craftItem(ItemStack(Items.REDSTONE), ItemStack(Items.PAPER), ItemStack(Items.GREEN_DYE)),
isStack(
DataComponentUtil.createStack(
ModRegistry.Items.DISK.get(),
DataComponents.DYED_COLOR,
DyedItemColor(Colour.GREEN.hex, false),
),
),
)
}
}

View File

@@ -111,6 +111,7 @@ object TestHooks {
Computer_Test::class.java, Computer_Test::class.java,
CraftOs_Test::class.java, CraftOs_Test::class.java,
Details_Test::class.java, Details_Test::class.java,
Disk_Test::class.java,
Disk_Drive_Test::class.java, Disk_Drive_Test::class.java,
Inventory_Test::class.java, Inventory_Test::class.java,
Loot_Test::class.java, Loot_Test::class.java,

View File

@@ -1,3 +1,23 @@
# New features in CC: Tweaked 1.116.0
* Add `turtle.getEquippedLeft()` and `turtle.getEquippedRight()`.
* Add item tags for floppy disks and pocket computers.
* Support multi-line strings and comments in `edit`.
* Computer and pocket computer terminal sizes can be set with the `computercraft:terminal_size` component.
* Border and sidebar textures now use vanilla's nine-sliced format.
Several bug fixes:
* Ignore shader compilation errors when running with Pojav.
* Fix several issues with character input.
* Fix pocket computer dyes being lost when equipping/unequipping upgrades.
* Fix superflous warnings from allocation tracking.
* Fix `__lt`/`__le` not working on heterogeneous types.
* Many documentation fixes (Lemmmy, matematikaadit, McJack12).
* Fix `0` being treated as a valid colour in `window` and `colour.toBlit`.
* Fix out-of-bounds when pasting too lon text.
* Fix syntax highlighting of string escapes (LorneHyde).
* Fix sidebar texture of advanced computers being offset.
# New features in CC: Tweaked 1.115.1 # New features in CC: Tweaked 1.115.1
* Update various translations (cyb3r, kevk2156, teamer337, yakku). * Update various translations (cyb3r, kevk2156, teamer337, yakku).

View File

@@ -1,11 +1,21 @@
New features in CC: Tweaked 1.115.1 New features in CC: Tweaked 1.116.0
* Update various translations (cyb3r, kevk2156, teamer337, yakku). * Add `turtle.getEquippedLeft()` and `turtle.getEquippedRight()`.
* Support Fabric's item lookup API for registering media providers. * Add item tags for floppy disks and pocket computers.
* Support multi-line strings and comments in `edit`.
* Computer and pocket computer terminal sizes can be set with the `computercraft:terminal_size` component.
* Border and sidebar textures now use vanilla's nine-sliced format.
Several bug fixes: Several bug fixes:
* Fix crashes on Create 6.0 (ellellie). * Ignore shader compilation errors when running with Pojav.
* Fix `speaker.playAudio` not updating speaker volume. * Fix several issues with character input.
* Resize pocket lectern textures to fix issues with generating mipmaps. * Fix pocket computer dyes being lost when equipping/unequipping upgrades.
* Fix superflous warnings from allocation tracking.
* Fix `__lt`/`__le` not working on heterogeneous types.
* Many documentation fixes (Lemmmy, matematikaadit, McJack12).
* Fix `0` being treated as a valid colour in `window` and `colour.toBlit`.
* Fix out-of-bounds when pasting too lon text.
* Fix syntax highlighting of string escapes (LorneHyde).
* Fix sidebar texture of advanced computers being offset.
Type "help changelog" to see the full version history. Type "help changelog" to see the full version history.

View File

@@ -0,0 +1,58 @@
-- SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
--
-- SPDX-License-Identifier: MPL-2.0
--[[- Launches a program, reports any errors, and prompts the user to close the
tab.
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
This is used by the `edit` program to launch the running code.
@tparam string title The title of the multishell tab.
@tparam string path The path to the file.
@tparam string contents The contents of the file.
@local
]]
return function(title, path, contents)
multishell.setTitle(multishell.getCurrent(), title)
local current = term.current()
local fn, err = load(contents, path, nil, _ENV)
if fn then
local exception = require "cc.internal.exception"
local ok, err, co = exception.try(fn)
term.redirect(current)
term.setTextColor(term.isColour() and colours.yellow or colours.white)
term.setBackgroundColor(colours.black)
term.setCursorBlink(false)
if not ok then
printError(err)
exception.report(err, co, { [path] = contents })
end
else
local parser = require "cc.internal.syntax"
if parser.parse_program(contents) then printError(err) end
end
local message = "Press any key to continue."
local _, y = term.getCursorPos()
local w, h = term.getSize()
local wrapped = require("cc.strings").wrap(message, w)
term.setTextColor(colours.white)
term.setBackgroundColor(colours.black)
local start_y = h - #wrapped + 1
if y >= start_y then term.scroll(y - start_y + 1) end
for i = 1, #wrapped do
term.setCursorPos(1, start_y + i - 1)
term.write(wrapped[i])
end
os.pullEvent('key')
require "cc.internal.event".discard_char()
end

View File

@@ -0,0 +1,125 @@
-- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe
--
-- SPDX-License-Identifier: LicenseRef-CCPL
--[[- A simple menu bar.
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
This provides a shared implementation of the menu bar used by the `edit` and
`paint` programs. This draws a menu bar at the bottom of the string, with a list
of options.
@local
]]
local expect = require "cc.expect".expect
--[[- Create a new menu bar.
This should be called every time the menu is displayed.
@tparam { string... } items The menu items to display.
@return The menu.
]]
local function create(items)
expect(1, items, "table")
return {
items = items,
selected = 1,
}
end
--[[- Draw the menu bar at the bottom of the screen.
This should be called when first displaying the menu, and if the whole screen is
redrawn (e.g. after a [`term_resize`]).
@param menu The menu bar to draw.
]]
local function draw(menu)
expect(1, menu, "table")
local _, height = term.getSize()
term.setCursorPos(1, height)
term.clearLine()
local active_colour = term.isColour() and colours.yellow or colours.white
term.setTextColour(colours.white)
for k, v in pairs(menu.items) do
if menu.selected == k then
term.setTextColour(active_colour)
term.write("[")
term.setTextColour(colours.white)
term.write(v)
term.setTextColour(active_colour)
term.write("]")
term.setTextColour(colours.white)
else
term.write(" " .. v .. " ")
end
end
end
--[[- Process an event.
@param menu The menu bar to update.
@tparam string The event name.
@param ... Additional arguments to the event.
@treturn nil|boolean|string Either:
- If no action was taken, return `nil`.
- If the menu was closed, return `false`.
- If an item was selected, return the item as a string.
]]
local function handle_event(menu, event, ...)
expect(1, menu, "table")
if event == "key" then
local key = ...
if key == keys.right then
-- Move right
menu.selected = menu.selected + 1
if menu.selected > #menu.items then menu.selected = 1 end
draw(menu)
elseif key == keys.left and menu.selected > 1 then
-- Move left
menu.selected = menu.selected - 1
if menu.selected < 1 then menu.selected = #menu.items end
draw(menu)
elseif key == keys.enter or key == keys.numPadEnter then
-- Select an option
return menu.items[menu.selected]
elseif key == keys.leftCtrl or keys == keys.rightCtrl or keys == keys.rightAlt then
-- Cancel the menu
return false
end
elseif event == "char" then
-- Select menu items
local char = (...):lower()
for _, item in pairs(menu.items) do
if item:sub(1, 1):lower() == char then return item end
end
elseif event == "mouse_click" then
local _, x, y = ...
local _, height = term.getSize()
if y ~= height then return false end -- Exit the menu
local item_start = 1
for _, item in ipairs(menu.items) do
local item_end = item_start + #item + 2
if x >= item_start and x < item_end then return item end
item_start = item_end
end
end
return nil
end
return { create = create, draw = draw, handle_event = handle_event }

View File

@@ -101,16 +101,21 @@ local function lex_number(context, str, start)
return tokens.NUMBER, pos - 1 return tokens.NUMBER, pos - 1
end end
--- Lex a quoted string. local lex_string_zap
--
-- @param context The current parser context. --[[- Lex a quoted string.
-- @tparam string str The string we're lexing.
-- @tparam number start_pos The start position of the string. @param context The current parser context.
-- @tparam string quote The quote character, either " or '. @tparam string str The string we're lexing.
-- @treturn number The token id for strings. @tparam number pos The position to start lexing from.
-- @treturn number The new position. @tparam number start_pos The actual start position of the string.
local function lex_string(context, str, start_pos, quote) @tparam string quote The quote character, either " or '.
local pos = start_pos + 1 @treturn number The token id for strings.
@treturn number The new position.
@treturn nil A placeholder value.
@treturn table|nil The continuation function when the string is not finished.
]]
local function lex_string(context, str, pos, start_pos, quote)
while true do while true do
local c = sub(str, pos, pos) local c = sub(str, pos, pos)
if c == quote then if c == quote then
@@ -125,15 +130,38 @@ local function lex_string(context, str, start_pos, quote)
pos = newline(context, str, pos + 1, c) pos = newline(context, str, pos + 1, c)
elseif c == "" then elseif c == "" then
context.report(errors.unfinished_string_escape, start_pos, pos, quote) context.report(errors.unfinished_string_escape, start_pos, pos, quote)
return tokens.STRING, pos return tokens.STRING, pos, nil, { lex_string, 1, 1, quote }
elseif c == "z" then elseif c == "z" then
return lex_string_zap(context, str, pos + 2, start_pos, quote)
else
pos = pos + 2 pos = pos + 2
end
else
pos = pos + 1
end
end
end
--[[- Lex the remainder of a zap escape sequence (`\z`). This consumes all leading
whitespace, and then continues lexing the string.
@param context The current parser context.
@tparam string str The string we're lexing.
@tparam number pos The position to start lexing from.
@tparam number start_pos The actual start position of the string.
@tparam string quote The quote character, either " or '.
@treturn number The token id for strings.
@treturn number The new position.
@treturn nil A placeholder value.
@treturn table|nil The continuation function when the string is not finished.
]]
lex_string_zap = function(context, str, pos, start_pos, quote)
while true do while true do
local next_pos, _, c = find(str, "([%S\r\n])", pos) local next_pos, _, c = find(str, "([%S\r\n])", pos)
if not next_pos then if not next_pos then
context.report(errors.unfinished_string, start_pos, #str, quote) context.report(errors.unfinished_string, start_pos, #str, quote)
return tokens.STRING, #str return tokens.STRING, #str, nil, { lex_string_zap, 1, 1, quote }
end end
if c == "\n" or c == "\r" then if c == "\n" or c == "\r" then
@@ -143,13 +171,8 @@ local function lex_string(context, str, start_pos, quote)
break break
end end
end end
else
pos = pos + 2 return lex_string(context, str, pos, start_pos, quote)
end
else
pos = pos + 1
end
end
end end
--- Consume the start or end of a long string. --- Consume the start or end of a long string.
@@ -205,6 +228,45 @@ local function lex_long_str(context, str, start, len)
end end
end end
--[[- Lex the remainder of a long string.
@param context The current parser context.
@tparam string str The string we're lexing.
@tparam number pos The position to start lexing from.
@tparam number start_pos The actual start position of the string.
@tparam number boundary_length The length of the boundary.
@treturn number The token id for strings.
@treturn number The new position.
@treturn nil A placeholder value.
@treturn table|nil The continuation function when the string is not finished.
]]
local function lex_long_string(context, str, pos, start_pos, boundary_length)
local end_pos = lex_long_str(context, str, pos, boundary_length)
if end_pos then return tokens.STRING, end_pos end
context.report(errors.unfinished_long_string, start_pos, pos - 1, boundary_length)
return tokens.STRING, #str, nil, { lex_long_string, 0, 0, boundary_length }
end
--[[- Lex the remainder of a long comment.
@param context The current parser context.
@tparam string str The comment we're lexing.
@tparam number pos The position to start lexing from.
@tparam number start_pos The actual start position of the comment.
@tparam number boundary_length The length of the boundary.
@treturn number The token id for comments.
@treturn number The new position.
@treturn nil A placeholder value.
@treturn table|nil The continuation function when the comment is not finished.
]]
local function lex_long_comment(context, str, pos, start_pos, boundary_length)
local end_pos = lex_long_str(context, str, pos, boundary_length)
if end_pos then return tokens.COMMENT, end_pos end
context.report(errors.unfinished_long_comment, start_pos, pos - 1, boundary_length)
return tokens.COMMENT, #str, nil, { lex_long_comment, 0, 0, boundary_length }
end
--- Lex a single token, assuming we have removed all leading whitespace. --- Lex a single token, assuming we have removed all leading whitespace.
-- --
@@ -229,16 +291,12 @@ local function lex_token(context, str, pos)
elseif c >= "0" and c <= "9" then return lex_number(context, str, pos) elseif c >= "0" and c <= "9" then return lex_number(context, str, pos)
-- Strings -- Strings
elseif c == "\"" or c == "\'" then return lex_string(context, str, pos, c) elseif c == "\"" or c == "\'" then return lex_string(context, str, pos + 1, pos, c)
elseif c == "[" then elseif c == "[" then
local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[") local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[")
if ok then -- Long string if ok then -- Long string
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - pos) return lex_long_string(context, str, boundary_pos + 1, pos, boundary_pos - pos)
if end_pos then return tokens.STRING, end_pos end
context.report(errors.unfinished_long_string, pos, boundary_pos, boundary_pos - pos)
return tokens.STRING, #str
elseif pos + 1 == boundary_pos then -- Just a "[" elseif pos + 1 == boundary_pos then -- Just a "["
return tokens.OSQUARE, pos return tokens.OSQUARE, pos
else -- Malformed long string, for instance "[=" else -- Malformed long string, for instance "[="
@@ -256,11 +314,7 @@ local function lex_token(context, str, pos)
if sub(str, comment_pos, comment_pos) == "[" then if sub(str, comment_pos, comment_pos) == "[" then
local ok, boundary_pos = lex_long_str_boundary(str, comment_pos + 1, "[") local ok, boundary_pos = lex_long_str_boundary(str, comment_pos + 1, "[")
if ok then if ok then
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - comment_pos) return lex_long_comment(context, str, boundary_pos + 1, pos, boundary_pos - comment_pos)
if end_pos then return tokens.COMMENT, end_pos end
context.report(errors.unfinished_long_comment, pos, boundary_pos, boundary_pos - comment_pos)
return tokens.COMMENT, #str
end end
end end
@@ -357,8 +411,8 @@ local function lex_one(context, str, pos)
elseif c == "\r" or c == "\n" then elseif c == "\r" or c == "\n" then
pos = newline(context, str, start_pos, c) pos = newline(context, str, start_pos, c)
else else
local token_id, end_pos, content = lex_token(context, str, start_pos) local token_id, end_pos, content, continue = lex_token(context, str, start_pos)
return token_id, start_pos, end_pos, content return token_id, start_pos, end_pos, content, continue
end end
end end
end end

View File

@@ -30,81 +30,40 @@ local x, y = 1, 1
local w, h = term.getSize() local w, h = term.getSize()
local scrollX, scrollY = 0, 0 local scrollX, scrollY = 0, 0
local tLines = {} local tLines, tLineLexStates = {}, {}
local bRunning = true local bRunning = true
-- Colours -- Colours
local highlightColour, keywordColour, commentColour, textColour, bgColour, stringColour, errorColour local isColour = term.isColour()
if term.isColour() then local highlightColour, keywordColour, textColour, bgColour, errorColour
if isColour then
bgColour = colours.black bgColour = colours.black
textColour = colours.white textColour = colours.white
highlightColour = colours.yellow highlightColour = colours.yellow
keywordColour = colours.yellow keywordColour = colours.yellow
commentColour = colours.green
stringColour = colours.red
errorColour = colours.red errorColour = colours.red
else else
bgColour = colours.black bgColour = colours.black
textColour = colours.white textColour = colours.white
highlightColour = colours.white highlightColour = colours.white
keywordColour = colours.white keywordColour = colours.white
commentColour = colours.white
stringColour = colours.white
errorColour = colours.white errorColour = colours.white
end end
local runHandler = [[multishell.setTitle(multishell.getCurrent(), %q)
local current = term.current()
local contents, name = %q, %q
local fn, err = load(contents, name, nil, _ENV)
if fn then
local exception = require "cc.internal.exception"
local ok, err, co = exception.try(fn, ...)
term.redirect(current)
term.setTextColor(term.isColour() and colours.yellow or colours.white)
term.setBackgroundColor(colours.black)
term.setCursorBlink(false)
if not ok then
printError(err)
exception.report(err, co, { [name] = contents })
end
else
local parser = require "cc.internal.syntax"
if parser.parse_program(contents) then printError(err) end
end
local message = "Press any key to continue."
if ok then message = "Program finished. " .. message end
local _, y = term.getCursorPos()
local w, h = term.getSize()
local wrapped = require("cc.strings").wrap(message, w)
local start_y = h - #wrapped + 1
if y >= start_y then term.scroll(y - start_y + 1) end
for i = 1, #wrapped do
term.setCursorPos(1, start_y + i - 1)
term.write(wrapped[i])
end
os.pullEvent('key')
require "cc.internal.event".discard_char()
]]
-- Menus -- Menus
local bMenu = false local menu = require "cc.internal.menu"
local nMenuItem = 1 local current_menu
local tMenuItems = {} local menu_items = {}
if not bReadOnly then if not bReadOnly then
table.insert(tMenuItems, "Save") table.insert(menu_items, "Save")
end end
if shell.openTab then if shell.openTab then
table.insert(tMenuItems, "Run") table.insert(menu_items, "Run")
end end
if peripheral.find("printer") then if peripheral.find("printer") then
table.insert(tMenuItems, "Print") table.insert(menu_items, "Print")
end end
table.insert(tMenuItems, "Exit") table.insert(menu_items, "Exit")
local status_ok, status_text local status_ok, status_text
local function set_status(text, ok) local function set_status(text, ok)
@@ -138,6 +97,7 @@ local function load(_sPath)
local sLine = file:read() local sLine = file:read()
while sLine do while sLine do
table.insert(tLines, sLine) table.insert(tLines, sLine)
table.insert(tLineLexStates, false)
sLine = file:read() sLine = file:read()
end end
file:close() file:close()
@@ -145,6 +105,7 @@ local function load(_sPath)
if #tLines == 0 then if #tLines == 0 then
table.insert(tLines, "") table.insert(tLines, "")
table.insert(tLineLexStates, false)
end end
end end
@@ -180,8 +141,9 @@ local tokens = require "cc.internal.syntax.parser".tokens
local lex_one = require "cc.internal.syntax.lexer".lex_one local lex_one = require "cc.internal.syntax.lexer".lex_one
local token_colours = { local token_colours = {
[tokens.STRING] = stringColour, [tokens.STRING] = isColour and colours.red or textColour,
[tokens.COMMENT] = commentColour, [tokens.COMMENT] = isColour and colours.green or colours.lightGrey,
[tokens.NUMBER] = isColour and colours.magenta or textColour,
-- Keywords -- Keywords
[tokens.AND] = keywordColour, [tokens.AND] = keywordColour,
[tokens.BREAK] = keywordColour, [tokens.BREAK] = keywordColour,
@@ -213,26 +175,6 @@ end
local lex_context = { line = function() end, report = function() end } local lex_context = { line = function() end, report = function() end }
local function writeHighlighted(line)
local pos, colour = 1, nil
while true do
local token, _, finish = lex_one(lex_context, line, pos)
if not token then break end
local new_colour = token_colours[token]
if new_colour ~= colour then
term.setTextColor(new_colour)
colour = new_colour
end
term.write(line:sub(pos, finish))
pos = finish + 1
end
term.write(line:sub(pos))
end
local tCompletions local tCompletions
local nCompletion local nCompletion
@@ -252,7 +194,7 @@ end
local function recomplete() local function recomplete()
local sLine = tLines[y] local sLine = tLines[y]
if not bMenu and not bReadOnly and x == #sLine + 1 then if not bReadOnly and x == #sLine + 1 then
tCompletions = complete(sLine) tCompletions = complete(sLine)
if tCompletions and #tCompletions > 0 then if tCompletions and #tCompletions > 0 then
nCompletion = 1 nCompletion = 1
@@ -276,34 +218,94 @@ local function writeCompletion(sLine)
end end
end end
local function redrawText() --- Check if two values are equal. If both values are lists, then the contents will be
local cursorX, cursorY = x, y -- checked for equality, to a depth of 1.
for y = 1, h - 1 do --
term.setCursorPos(1 - scrollX, y) -- @param x The first value.
-- @param x The second value.
-- @treturn boolean Whether the values are equal.
local function shallowEqual(x, y)
if x == y then return true end
if type(x) ~= "table" or type(y) ~= "table" then return false end
if #x ~= #y then return false end
for i = 1, #x do if x[i] ~= y[i] then return false end end
return true
end
local function redrawLines(line, endLine)
if not endLine then endLine = line end
local colour = term.getTextColour()
-- Highlight all lines between line and endLine, highlighting further lines if their
-- lexer state has changed and aborting at the end of the screen.
local changed = false
while (changed or line <= endLine) and line - scrollY < h do
term.setCursorPos(1 - scrollX, line - scrollY)
term.clearLine() term.clearLine()
local sLine = tLines[y + scrollY] local contents = tLines[line]
if sLine ~= nil then if not contents then break end
writeHighlighted(sLine)
if cursorY == y and cursorX == #sLine + 1 then -- Lex our first token, either taking our continuation state (if present) or
-- the default lexer.
local pos, token, _, finish, continuation = 1
local lex_state = tLineLexStates[line]
if lex_state then
token, finish, _, continuation = lex_state[1](lex_context, contents, table.unpack(lex_state, 2))
else
token, _, finish, _, continuation = lex_one(lex_context, contents, 1)
end
while token do
-- Print out that token
local new_colour = token_colours[token]
if new_colour ~= colour then
term.setTextColor(new_colour)
colour = new_colour
end
term.write(contents:sub(pos, finish))
pos = finish + 1
-- If we have a continuation, then we've reached the end of the line. Abort.
if continuation then break end
-- Otherwise lex another token and continue.
token, _, finish, _, continuation = lex_one(lex_context, contents, pos)
end
-- Print the rest of the line. We don't strictly speaking need this, as it will
-- only ever contain whitespace.
term.write(contents:sub(pos))
if line == y and x == #contents + 1 then
writeCompletion() writeCompletion()
colour = term.getTextColour()
end
line = line + 1
-- Update the lext state of the next line. If that has changed, then
-- re-highlight it too. We store the continuation as nil rather than
-- false, to ensure we use the array part of the table.
if continuation == nil then continuation = false end
if tLineLexStates[line] ~= nil and not shallowEqual(tLineLexStates[line], continuation) then
tLineLexStates[line] = continuation or false
changed = true
else
changed = false
end end
end end
end
term.setTextColor(colours.white)
term.setCursorPos(x - scrollX, y - scrollY) term.setCursorPos(x - scrollX, y - scrollY)
end end
local function redrawLine(_nY) local function redrawText()
local sLine = tLines[_nY] redrawLines(scrollY + 1, scrollY + h - 1)
if sLine then
term.setCursorPos(1 - scrollX, _nY - scrollY)
term.clearLine()
writeHighlighted(sLine)
if _nY == y and x == #sLine + 1 then
writeCompletion()
end
term.setCursorPos(x - scrollX, _nY - scrollY)
end
end end
local function redrawMenu() local function redrawMenu()
@@ -319,22 +321,9 @@ local function redrawMenu()
term.write(y) term.write(y)
term.setCursorPos(1, h) term.setCursorPos(1, h)
if bMenu then if current_menu then
-- Draw menu -- Draw menu
term.setTextColour(textColour) menu.draw(current_menu)
for nItem, sItem in pairs(tMenuItems) do
if nItem == nMenuItem then
term.setTextColour(highlightColour)
term.write("[")
term.setTextColour(textColour)
term.write(sItem)
term.setTextColour(highlightColour)
term.write("]")
term.setTextColour(textColour)
else
term.write(" " .. sItem .. " ")
end
end
else else
-- Draw status -- Draw status
term.setTextColour(status_ok and highlightColour or errorColour) term.setTextColour(status_ok and highlightColour or errorColour)
@@ -344,6 +333,7 @@ local function redrawMenu()
-- Reset cursor -- Reset cursor
term.setCursorPos(x - scrollX, y - scrollY) term.setCursorPos(x - scrollX, y - scrollY)
term.setCursorBlink(not current_menu)
end end
local tMenuFuncs = { local tMenuFuncs = {
@@ -421,7 +411,8 @@ local tMenuFuncs = {
end end
end end
bMenu = false local old_menu = current_menu
current_menu = nil
term.redirect(printerTerminal) term.redirect(printerTerminal)
local ok, error = pcall(function() local ok, error = pcall(function()
term.scroll() term.scroll()
@@ -439,7 +430,7 @@ local tMenuFuncs = {
redrawMenu() redrawMenu()
sleep(0.5) sleep(0.5)
end end
bMenu = true current_menu = old_menu
if nPage > 1 then if nPage > 1 then
set_status("Printed " .. nPage .. " Pages") set_status("Printed " .. nPage .. " Pages")
@@ -462,7 +453,8 @@ local tMenuFuncs = {
return return
end end
local ok = save(sTempPath, function(file) local ok = save(sTempPath, function(file)
file.write(runHandler:format(sTitle, table.concat(tLines, "\n"), "@/" .. sPath)) local runHandler = [[return require("cc.internal.edit_runner")(%q, %q, %q)]]
file.write(runHandler:format(sTitle, "@/" .. sPath, table.concat(tLines, "\n")))
end) end)
if ok then if ok then
local nTask = shell.openTab("/" .. sTempPath) local nTask = shell.openTab("/" .. sTempPath)
@@ -479,15 +471,6 @@ local tMenuFuncs = {
end, end,
} }
local function doMenuItem(_n)
tMenuFuncs[tMenuItems[_n]]()
if bMenu then
bMenu = false
term.setCursorBlink(true)
end
redrawMenu()
end
local function setCursor(newX, newY) local function setCursor(newX, newY)
local _, oldY = x, y local _, oldY = x, y
x, y = newX, newY x, y = newX, newY
@@ -519,12 +502,10 @@ local function setCursor(newX, newY)
if bRedraw then if bRedraw then
redrawText() redrawText()
elseif y ~= oldY then elseif y ~= oldY then
redrawLine(oldY) redrawLines(math.min(y, oldY), math.max(y, oldY))
redrawLine(y)
else else
redrawLine(y) redrawLines(y)
end end
term.setCursorPos(screenX, screenY)
redrawMenu() redrawMenu()
end end
@@ -550,20 +531,36 @@ local function acceptCompletion()
end end
end end
local function handleMenuEvent(event)
assert(current_menu)
local result = menu.handle_event(current_menu, table.unpack(event, 1, event.n))
if result == false then
current_menu = nil
redrawMenu()
elseif result ~= nil then
tMenuFuncs[result]()
current_menu = nil
redrawMenu()
end
end
-- Handle input -- Handle input
while bRunning do while bRunning do
local sEvent, param, param2, param3 = os.pullEvent() local event = table.pack(os.pullEvent())
if sEvent == "key" then if event[1] == "key" then
if param == keys.up then if current_menu then
-- Up handleMenuEvent(event)
if not bMenu then else
local key = event[2]
if key == keys.up then
if nCompletion then if nCompletion then
-- Cycle completions -- Cycle completions
nCompletion = nCompletion - 1 nCompletion = nCompletion - 1
if nCompletion < 1 then if nCompletion < 1 then
nCompletion = #tCompletions nCompletion = #tCompletions
end end
redrawLine(y) redrawLines(y)
elseif y > 1 then elseif y > 1 then
-- Move cursor up -- Move cursor up
@@ -572,19 +569,15 @@ while bRunning do
y - 1 y - 1
) )
end end
end
elseif param == keys.down then elseif key == keys.down then
-- Down
if not bMenu then
-- Move cursor down
if nCompletion then if nCompletion then
-- Cycle completions -- Cycle completions
nCompletion = nCompletion + 1 nCompletion = nCompletion + 1
if nCompletion > #tCompletions then if nCompletion > #tCompletions then
nCompletion = 1 nCompletion = 1
end end
redrawLine(y) redrawLines(y)
elseif y < #tLines then elseif y < #tLines then
-- Move cursor down -- Move cursor down
@@ -593,11 +586,8 @@ while bRunning do
y + 1 y + 1
) )
end end
end
elseif param == keys.tab then elseif key == keys.tab and not bReadOnly then
-- Tab
if not bMenu and not bReadOnly then
if nCompletion and x == #tLines[y] + 1 then if nCompletion and x == #tLines[y] + 1 then
-- Accept autocomplete -- Accept autocomplete
acceptCompletion() acceptCompletion()
@@ -607,11 +597,8 @@ while bRunning do
tLines[y] = string.sub(sLine, 1, x - 1) .. " " .. string.sub(sLine, x) tLines[y] = string.sub(sLine, 1, x - 1) .. " " .. string.sub(sLine, x)
setCursor(x + 4, y) setCursor(x + 4, y)
end end
end
elseif param == keys.pageUp then elseif key == keys.pageUp then
-- Page Up
if not bMenu then
-- Move up a page -- Move up a page
local newY local newY
if y - (h - 1) >= 1 then if y - (h - 1) >= 1 then
@@ -623,11 +610,7 @@ while bRunning do
math.min(x, #tLines[newY] + 1), math.min(x, #tLines[newY] + 1),
newY newY
) )
end elseif key == keys.pageDown then
elseif param == keys.pageDown then
-- Page Down
if not bMenu then
-- Move down a page -- Move down a page
local newY local newY
if y + (h - 1) <= #tLines then if y + (h - 1) <= #tLines then
@@ -637,48 +620,29 @@ while bRunning do
end end
local newX = math.min(x, #tLines[newY] + 1) local newX = math.min(x, #tLines[newY] + 1)
setCursor(newX, newY) setCursor(newX, newY)
end
elseif param == keys.home then elseif key == keys.home then
-- Home
if not bMenu then
-- Move cursor to the beginning -- Move cursor to the beginning
if x > 1 then if x > 1 then
setCursor(1, y) setCursor(1, y)
end end
end
elseif param == keys["end"] then elseif key == keys["end"] then
-- End
if not bMenu then
-- Move cursor to the end -- Move cursor to the end
local nLimit = #tLines[y] + 1 local nLimit = #tLines[y] + 1
if x < nLimit then if x < nLimit then
setCursor(nLimit, y) setCursor(nLimit, y)
end end
end
elseif param == keys.left then elseif key == keys.left then
-- Left
if not bMenu then
if x > 1 then if x > 1 then
-- Move cursor left -- Move cursor left
setCursor(x - 1, y) setCursor(x - 1, y)
elseif x == 1 and y > 1 then elseif x == 1 and y > 1 then
setCursor(#tLines[y - 1] + 1, y - 1) setCursor(#tLines[y - 1] + 1, y - 1)
end end
else
-- Move menu left
nMenuItem = nMenuItem - 1
if nMenuItem < 1 then
nMenuItem = #tMenuItems
end
redrawMenu()
end
elseif param == keys.right then elseif key == keys.right then
-- Right
if not bMenu then
local nLimit = #tLines[y] + 1 local nLimit = #tLines[y] + 1
if x < nLimit then if x < nLimit then
-- Move cursor right -- Move cursor right
@@ -690,35 +654,23 @@ while bRunning do
-- Go to next line -- Go to next line
setCursor(1, y + 1) setCursor(1, y + 1)
end end
else
-- Move menu right
nMenuItem = nMenuItem + 1
if nMenuItem > #tMenuItems then
nMenuItem = 1
end
redrawMenu()
end
elseif param == keys.delete then elseif key == keys.delete and not bReadOnly then
-- Delete
if not bMenu and not bReadOnly then
local nLimit = #tLines[y] + 1 local nLimit = #tLines[y] + 1
if x < nLimit then if x < nLimit then
local sLine = tLines[y] local sLine = tLines[y]
tLines[y] = string.sub(sLine, 1, x - 1) .. string.sub(sLine, x + 1) tLines[y] = string.sub(sLine, 1, x - 1) .. string.sub(sLine, x + 1)
recomplete() recomplete()
redrawLine(y) redrawLines(y)
elseif y < #tLines then elseif y < #tLines then
tLines[y] = tLines[y] .. tLines[y + 1] tLines[y] = tLines[y] .. tLines[y + 1]
table.remove(tLines, y + 1) table.remove(tLines, y + 1)
table.remove(tLineLexStates, y + 1)
recomplete() recomplete()
redrawText() redrawText()
end end
end
elseif param == keys.backspace then elseif key == keys.backspace and not bReadOnly then
-- Backspace
if not bMenu and not bReadOnly then
if x > 1 then if x > 1 then
-- Remove character -- Remove character
local sLine = tLines[y] local sLine = tLines[y]
@@ -734,14 +686,12 @@ while bRunning do
local sPrevLen = #tLines[y - 1] local sPrevLen = #tLines[y - 1]
tLines[y - 1] = tLines[y - 1] .. tLines[y] tLines[y - 1] = tLines[y - 1] .. tLines[y]
table.remove(tLines, y) table.remove(tLines, y)
table.remove(tLineLexStates, y)
setCursor(sPrevLen + 1, y - 1) setCursor(sPrevLen + 1, y - 1)
redrawText() redrawText()
end end
end
elseif param == keys.enter or param == keys.numPadEnter then elseif (key == keys.enter or key == keys.numPadEnter) and not bReadOnly then
-- Enter/Numpad Enter
if not bMenu and not bReadOnly then
-- Newline -- Newline
local sLine = tLines[y] local sLine = tLines[y]
local _, spaces = string.find(sLine, "^[ ]+") local _, spaces = string.find(sLine, "^[ ]+")
@@ -750,99 +700,60 @@ while bRunning do
end end
tLines[y] = string.sub(sLine, 1, x - 1) tLines[y] = string.sub(sLine, 1, x - 1)
table.insert(tLines, y + 1, string.rep(' ', spaces) .. string.sub(sLine, x)) table.insert(tLines, y + 1, string.rep(' ', spaces) .. string.sub(sLine, x))
table.insert(tLineLexStates, y + 1, false)
setCursor(spaces + 1, y + 1) setCursor(spaces + 1, y + 1)
redrawText() redrawText()
elseif bMenu then elseif key == keys.leftCtrl or key == keys.rightCtrl then
-- Menu selection current_menu = menu.create(menu_items)
doMenuItem(nMenuItem)
end
elseif param == keys.leftCtrl or param == keys.rightCtrl then
-- Menu toggle
bMenu = not bMenu
if bMenu then
term.setCursorBlink(false)
else
term.setCursorBlink(true)
end
redrawMenu()
elseif param == keys.rightAlt then
if bMenu then
bMenu = false
term.setCursorBlink(true)
redrawMenu() redrawMenu()
end end
end end
elseif event[1] == "char" then
elseif sEvent == "char" then if current_menu then
if not bMenu and not bReadOnly then handleMenuEvent(event)
elseif not bReadOnly then
-- Input text -- Input text
local sLine = tLines[y] local sLine = tLines[y]
tLines[y] = string.sub(sLine, 1, x - 1) .. param .. string.sub(sLine, x) tLines[y] = string.sub(sLine, 1, x - 1) .. event[2] .. string.sub(sLine, x)
setCursor(x + 1, y) setCursor(x + 1, y)
elseif bMenu then
-- Select menu items
for n, sMenuItem in ipairs(tMenuItems) do
if string.lower(string.sub(sMenuItem, 1, 1)) == string.lower(param) then
doMenuItem(n)
break
end
end
end end
elseif sEvent == "paste" then elseif event[1] == "paste" and not bReadOnly then
if not bReadOnly then
-- Close menu if open -- Close menu if open
if bMenu then if current_menu then
bMenu = false current_menu = nil
term.setCursorBlink(true)
redrawMenu() redrawMenu()
end end
-- Input text
local sLine = tLines[y]
tLines[y] = string.sub(sLine, 1, x - 1) .. param .. string.sub(sLine, x)
setCursor(x + #param, y)
end
elseif sEvent == "mouse_click" then -- Input text
local cx, cy = param2, param3 local text = event[2]
if not bMenu then local sLine = tLines[y]
if param == 1 then tLines[y] = string.sub(sLine, 1, x - 1) .. text .. string.sub(sLine, x)
setCursor(x + #text, y)
elseif event[1] == "mouse_click" then
local button, cx, cy = event[2], event[3], event[4]
if current_menu then
handleMenuEvent(event)
else
if button == 1 then
-- Left click -- Left click
if cy < h then if cy < h then
local newY = math.min(math.max(scrollY + cy, 1), #tLines) local newY = math.min(math.max(scrollY + cy, 1), #tLines)
local newX = math.min(math.max(scrollX + cx, 1), #tLines[newY] + 1) local newX = math.min(math.max(scrollX + cx, 1), #tLines[newY] + 1)
setCursor(newX, newY) setCursor(newX, newY)
else else
bMenu = true current_menu = menu.create(menu_items)
redrawMenu() redrawMenu()
end end
end end
else
if cy == h then
local nMenuPosEnd = 1
local nMenuPosStart = 1
for n, sMenuItem in ipairs(tMenuItems) do
nMenuPosEnd = nMenuPosEnd + #sMenuItem + 1
if cx > nMenuPosStart and cx < nMenuPosEnd then
doMenuItem(n)
end
nMenuPosEnd = nMenuPosEnd + 1
nMenuPosStart = nMenuPosEnd
end
else
bMenu = false
term.setCursorBlink(true)
redrawMenu()
end
end end
elseif sEvent == "mouse_scroll" then elseif event[1] == "mouse_scroll" then
if not bMenu then if not current_menu then
if param == -1 then local direction = event[2]
if direction == -1 then
-- Scroll up -- Scroll up
if scrollY > 0 then if scrollY > 0 then
-- Move cursor up -- Move cursor up
@@ -850,7 +761,7 @@ while bRunning do
redrawText() redrawText()
end end
elseif param == 1 then elseif direction == 1 then
-- Scroll down -- Scroll down
local nMaxScroll = #tLines - (h - 1) local nMaxScroll = #tLines - (h - 1)
if scrollY < nMaxScroll then if scrollY < nMaxScroll then
@@ -862,7 +773,7 @@ while bRunning do
end end
end end
elseif sEvent == "term_resize" then elseif event[1] == "term_resize" then
w, h = term.getSize() w, h = term.getSize()
setCursor(x, y) setCursor(x, y)
redrawMenu() redrawMenu()

View File

@@ -20,6 +20,7 @@ local canvasColour = colours.black
local canvas = {} local canvas = {}
-- The menu options -- The menu options
local menu = require "cc.internal.menu"
local mChoices = { "Save", "Exit" } local mChoices = { "Save", "Exit" }
-- The message displayed in the footer bar -- The message displayed in the footer bar
@@ -299,10 +300,7 @@ local menu_choices = {
end end
return false return false
end, end,
Exit = function() Exit = function() return true end,
require "cc.internal.event".discard_char() -- Consume stray "char" events from pressing Ctrl then E separately.
return true
end,
} }
--[[ --[[
@@ -310,80 +308,25 @@ local menu_choices = {
returns: true if the program is to be exited; false otherwise returns: true if the program is to be exited; false otherwise
]] ]]
local function accessMenu() local function accessMenu()
-- Selected menu option local current_menu = menu.create(mChoices)
local selection = 1
term.setBackgroundColour(colours.black) term.setBackgroundColour(colours.black)
menu.draw(current_menu)
while true do while true do
-- Draw the menu
term.setCursorPos(1, h)
term.clearLine()
term.setTextColour(colours.white)
for k, v in pairs(mChoices) do
if selection == k then
term.setTextColour(colours.yellow)
term.write("[")
term.setTextColour(colours.white)
term.write(v)
term.setTextColour(colours.yellow)
term.write("]")
term.setTextColour(colours.white)
else
term.write(" " .. v .. " ")
end
end
-- Handle input in the menu -- Handle input in the menu
local id, param1, param2, param3 = os.pullEvent() local event = table.pack(os.pullEvent())
if id == "key" then
local key = param1
-- Handle menu shortcuts. local result = menu.handle_event(current_menu, table.unpack(event, 1, event.n))
for _, menu_item in ipairs(mChoices) do if result == false then
local k = keys[menu_item:sub(1, 1):lower()]
if k and k == key then
return menu_choices[menu_item]()
end
end
if key == keys.right then
-- Move right
selection = selection + 1
if selection > #mChoices then
selection = 1
end
elseif key == keys.left and selection > 1 then
-- Move left
selection = selection - 1
if selection < 1 then
selection = #mChoices
end
elseif key == keys.enter or key == keys.numPadEnter then
-- Select an option
return menu_choices[mChoices[selection]]()
elseif key == keys.leftCtrl or keys == keys.rightCtrl then
-- Cancel the menu
return false return false
elseif result ~= nil then
return menu_choices[result]()
end end
elseif id == "mouse_click" then
local cx, cy = param2, param3
if cy ~= h then return false end -- Exit the menu
local nMenuPosEnd = 1 if event[1] == "term_resize" then
local nMenuPosStart = 1
for _, sMenuItem in ipairs(mChoices) do
nMenuPosEnd = nMenuPosEnd + #sMenuItem + 1
if cx > nMenuPosStart and cx < nMenuPosEnd then
return menu_choices[sMenuItem]()
end
nMenuPosEnd = nMenuPosEnd + 1
nMenuPosStart = nMenuPosEnd
end
elseif id == "term_resize" then
termResize() termResize()
menu.draw(current_menu)
end end
end end
end end

View File

@@ -300,7 +300,7 @@ function shell.setDir(dir)
sDir = fs.combine(dir, "") sDir = fs.combine(dir, "")
end end
--- Set the path where programs are located. --- Get the path where programs are located.
-- --
-- The path is composed of a list of directory names in a string, each separated -- The path is composed of a list of directory names in a string, each separated
-- by a colon (`:`). On normal turtles will look in the current directory (`.`), -- by a colon (`:`). On normal turtles will look in the current directory (`.`),

View File

@@ -70,18 +70,33 @@ We expected a closing delimiter (]=]) somewhere after this comment was started.
1:1-1:5 COMMENT --[=[ 1:1-1:5 COMMENT --[=[
``` ```
But incomplete opening `[`s are treated as a line comment:
```lua
--[
--[=
return
```
```txt
1:1-1:3 COMMENT --[
2:1-2:4 COMMENT --[=
3:1-3:6 RETURN return
```
Nested comments are rejected, just as Lua 5.1 does: Nested comments are rejected, just as Lua 5.1 does:
```lua ```lua
--[[ [[ ]] --[[ [[ ]] return
``` ```
```txt ```txt
[[ cannot be nested inside another [[ ... ]] [[ cannot be nested inside another [[ ... ]]
| |
1 | --[[ [[ ]] 1 | --[[ [[ ]] return
| ^^ | ^^
1:1-1:10 COMMENT --[[ [[ ]] 1:1-1:10 COMMENT --[[ [[ ]]
1:12-1:17 RETURN return
``` ```
# Strings # Strings

View File

@@ -56,7 +56,7 @@ import { type DataExport, WithExport } from "./components/WithExport";
for (const file of await fs.readdir(sourceDir, { withFileTypes: true, recursive: true })) { for (const file of await fs.readdir(sourceDir, { withFileTypes: true, recursive: true })) {
if(!file.isFile() || !file.name.endsWith(".html")) continue; if(!file.isFile() || !file.name.endsWith(".html")) continue;
const sourcePath = path.join(file.path, file.name); const sourcePath = path.join(file.parentPath, file.name);
const contents = await fs.readFile(sourcePath, "utf-8"); const contents = await fs.readFile(sourcePath, "utf-8");
const { result } = await processor.process(contents); const { result } = await processor.process(contents);