1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-06-08 17:44:11 +00:00

Pull in CC:T 1.108.1, some other fixes

- Use Cobalt's new Java patcher, allowing us to use Java 17 syntax. As
   such, update a couple classes to make use of that.
 - Pull in latest ROM. This is very noisy (due to the link syntax
   changes), but mostly trivial changes.
 - Fix wget and pastebin programs using http methods which don't exist.
 - Make fs.open create the parent directory when opening for write, much
   like newer CC versions.
This commit is contained in:
Jonathan Coates 2023-10-04 18:56:11 +01:00
parent 35e227ed02
commit 136fbd2589
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
59 changed files with 774 additions and 1006 deletions

View File

@ -1,27 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
plugins {
application
alias(libs.plugins.kotlin)
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
repositories {
mavenCentral()
}
dependencies {
implementation(libs.bundles.asm)
implementation(libs.bundles.kotlin)
}
tasks.jar {
manifest.attributes("Main-Class" to "cc.tweaked.build.MainKt")
}

View File

@ -1,61 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.build
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.util.CheckClassAdapter
import java.nio.file.Files
import java.nio.file.Path
/** Generate additional classes which don't exist in the original source set. */
interface ClassEmitter {
/** Emit a class if it does not already exist. */
fun generate(name: String, classReader: ClassReader? = null, flags: Int = 0, write: (ClassVisitor) -> Unit)
}
/** An implementation of [ClassEmitter] which writes files to a directory. */
class FileClassEmitter(private val outputDir: Path) : ClassEmitter {
private val emitted = mutableSetOf<String>()
override fun generate(name: String, classReader: ClassReader?, flags: Int, write: (ClassVisitor) -> Unit) {
if (!emitted.add(name)) return
val cw = NonLoadingClassWriter(classReader, flags)
write(CheckClassAdapter(cw))
val outputFile = outputDir.resolve("$name.class")
Files.createDirectories(outputFile.parent)
Files.write(outputFile, cw.toByteArray())
}
}
/** A unordered pair, such that (x, y) = (y, x) */
private class UnorderedPair<T>(private val x: T, private val y: T) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is cc.tweaked.build.UnorderedPair<*>) return false
return (x == other.x && y == other.y) || (x == other.y && y == other.x)
}
override fun hashCode(): Int = x.hashCode() xor y.hashCode()
override fun toString(): String = "UnorderedPair($x, $y)"
}
private val subclassRelations = mapOf<UnorderedPair<String>, String>(
)
/** A [ClassWriter] extension which avoids loading classes when computing frames. */
private class NonLoadingClassWriter(reader: ClassReader?, flags: Int) : ClassWriter(reader, flags) {
override fun getCommonSuperClass(type1: String, type2: String): String {
if (type1 == "java/lang/Object" || type2 == "java/lang/Object") return "java/lang/Object"
val subclass = subclassRelations[UnorderedPair(type1, type2)]
if (subclass != null) return subclass
println("[WARN] Guessing the super-class of $type1 and $type2.")
return "java/lang/Object"
}
}

View File

@ -1,29 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.build
import org.objectweb.asm.ClassReader
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.io.path.extension
import kotlin.system.exitProcess
fun main(args: Array<String>) {
if (args.size != 2) {
System.err.println("Expected: INPUT OUTPUT")
exitProcess(1)
}
val inputDir = Paths.get(args[0])
val outputDir = Paths.get(args[1])
val emitter = FileClassEmitter(outputDir)
Files.find(inputDir, Int.MAX_VALUE, { path, _ -> path.extension == "class" }).use { files ->
files.forEach { inputFile ->
val reader = Files.newInputStream(inputFile).use { ClassReader(it) }
emitter.generate(reader.className, flags = 0) { cw -> reader.accept(Unlambda(emitter, cw), 0) }
}
}
}

View File

@ -1,210 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.build
import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.*
class Unlambda(private val emitter: ClassEmitter, visitor: ClassVisitor) :
ClassVisitor(ASM9, visitor) {
internal lateinit var className: String
private var isInterface: Boolean = false
private var lambda = 0
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String, interfaces: Array<out String>?) {
super.visit(V1_6, access, name, signature, superName, interfaces)
if (version != V1_8) throw IllegalStateException("Expected Java version 8")
className = name
isInterface = (access and ACC_INTERFACE) != 0
}
override fun visitMethod(access: Int, name: String, descriptor: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
val access = if (access.and(ACC_STATIC) != 0) access.and(ACC_PRIVATE.inv()) else access
val mw = super.visitMethod(access, name, descriptor, signature, exceptions) ?: return null
if (isInterface && name != "<clinit>") {
if ((access and ACC_STATIC) != 0) println("[WARN] $className.$name is a static method")
else if ((access and ACC_ABSTRACT) == 0) println("[WARN] $className.$name is a default method")
}
return UnlambdaMethodVisitor(this, emitter, mw)
}
internal fun nextLambdaName(): String {
val name = "lambda$lambda"
lambda++
return name
}
}
internal class UnlambdaMethodVisitor(
private val parent: Unlambda,
private val emitter: ClassEmitter,
methodVisitor: MethodVisitor,
) : MethodVisitor(ASM9, methodVisitor) {
private class Bridge(val lambda: Handle, val bridgeName: String)
private val bridgeMethods = mutableListOf<Bridge>()
override fun visitMethodInsn(opcode: Int, owner: String, name: String, descriptor: String, isInterface: Boolean) {
if (opcode == INVOKESTATIC && isInterface) println("[WARN] Invoke interface $owner.$name in ${parent.className}")
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
override fun visitInvokeDynamicInsn(name: String, descriptor: String, handle: Handle, vararg arguments: Any) {
if (handle.owner == "java/lang/invoke/LambdaMetafactory" && handle.name == "metafactory" && handle.desc == "(Ljava/lang/invoke/MethodHandles\$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;") {
visitLambda(name, descriptor, arguments[0] as Type, arguments[1] as Handle)
} else {
super.visitInvokeDynamicInsn(name, descriptor, handle, *arguments)
}
}
private fun visitLambda(name: String, descriptor: String, signature: Type, lambda: Handle) {
val interfaceTy = Type.getReturnType(descriptor)
val fields = Type.getArgumentTypes(descriptor)
val lambdaName = parent.nextLambdaName()
val className = "${parent.className}\$$lambdaName"
val bridgeName = "${lambdaName}Bridge"
emitter.generate(className, flags = ClassWriter.COMPUTE_MAXS) { cw ->
cw.visit(V1_6, ACC_FINAL, className, null, "java/lang/Object", arrayOf(interfaceTy.internalName))
for ((i, ty) in fields.withIndex()) {
cw.visitField(ACC_PRIVATE or ACC_FINAL, "field$i", ty.descriptor, null, null)
.visitEnd()
}
cw.visitMethod(ACC_STATIC, "create", Type.getMethodDescriptor(interfaceTy, *fields), null, null).let { mw ->
mw.visitCode()
mw.visitTypeInsn(NEW, className)
mw.visitInsn(DUP)
for ((i, ty) in fields.withIndex()) mw.visitVarInsn(ty.getOpcode(ILOAD), i)
mw.visitMethodInsn(INVOKESPECIAL, className, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, *fields), false)
mw.visitInsn(ARETURN)
mw.visitMaxs(0, 0)
mw.visitEnd()
}
cw.visitMethod(0, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, *fields), null, null).let { mw ->
mw.visitCode()
mw.visitVarInsn(ALOAD, 0)
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
for ((i, ty) in fields.withIndex()) {
mw.visitVarInsn(ALOAD, 0)
mw.visitVarInsn(ty.getOpcode(ILOAD), i + 1)
mw.visitFieldInsn(PUTFIELD, className, "field$i", ty.descriptor)
}
mw.visitInsn(RETURN)
mw.visitMaxs(0, 0)
mw.visitEnd()
}
cw.visitMethod(ACC_PUBLIC, name, signature.descriptor, null, null).let { mw ->
mw.visitCode()
val targetArgs = when (lambda.tag) {
H_INVOKEVIRTUAL, H_INVOKESPECIAL -> arrayOf(
Type.getObjectType(lambda.owner),
*Type.getArgumentTypes(lambda.desc),
)
H_INVOKESTATIC, H_NEWINVOKESPECIAL -> Type.getArgumentTypes(lambda.desc)
else -> throw IllegalStateException("Unhandled opcode")
}
var targetArgOffset = 0
// If we're a ::new method handle, create the object.
if (lambda.tag == H_NEWINVOKESPECIAL) {
mw.visitTypeInsn(NEW, lambda.owner)
mw.visitInsn(DUP)
}
// Load our fields
for ((i, ty) in fields.withIndex()) {
mw.visitVarInsn(ALOAD, 0)
mw.visitFieldInsn(GETFIELD, className, "field$i", ty.descriptor)
val expectedTy = targetArgs[targetArgOffset]
if (ty != expectedTy) println("$ty != $expectedTy")
targetArgOffset++
}
// Load the additional arguments
val arguments = signature.argumentTypes
for ((i, ty) in arguments.withIndex()) {
mw.visitVarInsn(ty.getOpcode(ILOAD), i + 1)
val expectedTy = targetArgs[targetArgOffset]
if (ty != expectedTy) {
println("[WARN] $ty != $expectedTy, adding a cast")
mw.visitTypeInsn(CHECKCAST, expectedTy.internalName)
}
targetArgOffset++
}
// Invoke our init call
mw.visitMethodInsn(
when (lambda.tag) {
H_INVOKEVIRTUAL, H_INVOKESPECIAL -> INVOKEVIRTUAL
H_INVOKESTATIC -> INVOKESTATIC
H_NEWINVOKESPECIAL -> INVOKESPECIAL
else -> throw IllegalStateException("Unhandled opcode")
},
lambda.owner, if (lambda.tag == H_INVOKESPECIAL) bridgeName else lambda.name, lambda.desc, false,
)
if (lambda.tag != H_NEWINVOKESPECIAL) {
val expectedRetTy = signature.returnType
val retTy = Type.getReturnType(lambda.desc)
if (expectedRetTy != retTy) {
// println("[WARN] $retTy != $expectedRetTy, adding a cast")
if (retTy == Type.INT_TYPE && expectedRetTy.descriptor == "Ljava/lang/Object;") {
mw.visitMethodInsn(INVOKESTATIC, "jav/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)
} else {
// println("[ERROR] Unhandled")
}
}
}
// A little ugly special handling for ::new
mw.visitInsn(
if (lambda.tag == H_NEWINVOKESPECIAL) ARETURN else signature.returnType.getOpcode(IRETURN),
)
mw.visitMaxs(0, 0)
mw.visitEnd()
}
cw.visitEnd()
}
// If we're a ::new method handle, create the object.
if (lambda.tag == H_INVOKESPECIAL) {
bridgeMethods.add(Bridge(lambda, bridgeName))
}
visitMethodInsn(INVOKESTATIC, className, "create", Type.getMethodDescriptor(interfaceTy, *fields), false)
}
override fun visitEnd() {
super.visitEnd()
for (bridge in bridgeMethods) {
println("[INFO] Using bridge method ${bridge.bridgeName} for ${bridge.lambda}")
val mw = parent.visitMethod(ACC_PUBLIC, bridge.bridgeName, bridge.lambda.desc, null, null) ?: continue
mw.visitCode()
mw.visitVarInsn(ALOAD, 0)
for ((i, ty) in Type.getArgumentTypes(bridge.lambda.desc)
.withIndex()) mw.visitVarInsn(ty.getOpcode(ILOAD), i + 1)
mw.visitMethodInsn(INVOKESPECIAL, bridge.lambda.owner, bridge.lambda.name, bridge.lambda.desc, false)
mw.visitInsn(Type.getReturnType(bridge.lambda.desc).getOpcode(IRETURN))
val size = 1 + Type.getArgumentTypes(bridge.lambda.desc).size
mw.visitMaxs(size, size)
mw.visitEnd()
}
}
}

View File

@ -20,13 +20,16 @@ version = modVersion
base.archivesName.convention("cc-tweaked-$mcVersion") base.archivesName.convention("cc-tweaked-$mcVersion")
java { java {
// Last version able to set a --release as low as 6 toolchain {
toolchain.languageVersion.set(JavaLanguageVersion.of(11)) languageVersion.set(JavaLanguageVersion.of(17))
}
withSourcesJar() withSourcesJar()
} }
tasks.compileJava { options.release.set(8) } val runtimeToolchain = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(8))
}
repositories { repositories {
mavenCentral() mavenCentral()
@ -38,10 +41,6 @@ repositories {
} }
volde { volde {
forgeCapabilities {
setSrgsAsFallback(true)
}
runs { runs {
named("client") { named("client") {
programArg("SquidDev") programArg("SquidDev")
@ -50,6 +49,10 @@ volde {
} }
} }
tasks.withType(net.fabricmc.loom.task.RunTask::class.java).configureEach {
javaLauncher.set(runtimeToolchain)
}
configurations { configurations {
val shade by registering val shade by registering
compileOnly { extendsFrom(shade.get()) } compileOnly { extendsFrom(shade.get()) }
@ -71,10 +74,12 @@ dependencies {
}, },
) )
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
compileOnly("org.jetbrains:annotations:24.0.1")
modImplementation("maven.modrinth:computercraft:1.50") modImplementation("maven.modrinth:computercraft:1.50")
"shade"("org.squiddev:Cobalt") "shade"("org.squiddev:Cobalt")
"buildTools"(project(":build-tools")) "buildTools"("cc.tweaked.cobalt:build-tools")
} }
// Point compileJava to emit to classes/uninstrumentedJava/main, and then add a task to instrument these classes, // Point compileJava to emit to classes/uninstrumentedJava/main, and then add a task to instrument these classes,
@ -88,12 +93,9 @@ val instrumentJava = tasks.register(mainSource.getTaskName("Instrument", "Java")
inputs.dir(untransformedClasses).withPropertyName("inputDir") inputs.dir(untransformedClasses).withPropertyName("inputDir")
outputs.dir(javaClassesDir).withPropertyName("outputDir") outputs.dir(javaClassesDir).withPropertyName("outputDir")
javaLauncher.set( // Run under Java 8, so we can check compatibility of methods.
javaToolchains.launcherFor { javaLauncher.set(runtimeToolchain)
languageVersion.set(JavaLanguageVersion.of(17)) mainClass.set("cc.tweaked.cobalt.build.MainKt")
},
)
mainClass.set("cc.tweaked.build.MainKt")
classpath = buildTools classpath = buildTools
args = listOf( args = listOf(
@ -129,6 +131,8 @@ tasks.jar {
} }
tasks.processResources { tasks.processResources {
inputs.property("version", project.version)
inputs.property("mcVersion", mcVersion)
filesMatching("mcmod.info") { filesMatching("mcmod.info") {
expand("version" to project.version, "mcVersion" to mcVersion) expand("version" to project.version, "mcVersion" to mcVersion)
} }

View File

@ -4,6 +4,6 @@
org.gradle.jvmargs=-Xmx3G org.gradle.jvmargs=-Xmx3G
modVersion=1.105.1 modVersion=1.108.1
mcVersion=1.4.7 mcVersion=1.4.7

View File

@ -38,5 +38,3 @@ val mcVersion: String by settings
rootProject.name = "cc-tweaked-$mcVersion" rootProject.name = "cc-tweaked-$mcVersion"
includeBuild("vendor/Cobalt") includeBuild("vendor/Cobalt")
include("build-tools")

View File

@ -4,8 +4,6 @@
package dan200.computercraft.api.lua; package dan200.computercraft.api.lua;
import java.util.Objects;
/** /**
* A wrapper type for "coerced" values. * A wrapper type for "coerced" values.
* <p> * <p>
@ -20,27 +18,9 @@ import java.util.Objects;
* } * }
* }</pre> * }</pre>
* *
* @param value The argument value.
* @param <T> The type of the underlying value. * @param <T> The type of the underlying value.
* @see IArguments#getStringCoerced(int) * @see IArguments#getStringCoerced(int)
*/ */
public final class Coerced<T> { public record Coerced<T>(T value) {
private final T value;
public Coerced(T value) {
this.value = value;
}
public T value() {
return value;
}
@Override
public boolean equals(Object obj) {
return obj == this || obj instanceof Coerced && Objects.equals(value, ((Coerced<?>) obj).value);
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
} }

View File

@ -4,6 +4,9 @@
package dan200.computercraft.api.lua; package dan200.computercraft.api.lua;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -39,6 +42,7 @@ public abstract class IArguments {
* @throws IllegalStateException If accessing these arguments outside the scope of the original function. See * @throws IllegalStateException If accessing these arguments outside the scope of the original function. See
* {@link #escapes()}. * {@link #escapes()}.
*/ */
@Nullable
public abstract Object get(int index) throws LuaException; public abstract Object get(int index) throws LuaException;
/** /**
@ -70,8 +74,8 @@ public abstract class IArguments {
* @see #get(int) To get a single argument. * @see #get(int) To get a single argument.
*/ */
public Object[] getAll() throws LuaException { public Object[] getAll() throws LuaException {
Object[] result = new Object[count()]; var result = new Object[count()];
for (int i = 0; i < result.length; i++) result[i] = get(i); for (var i = 0; i < result.length; i++) result[i] = get(i);
return result; return result;
} }
@ -84,9 +88,9 @@ public abstract class IArguments {
* @see #getFiniteDouble(int) if you require this to be finite (i.e. not infinite or NaN). * @see #getFiniteDouble(int) if you require this to be finite (i.e. not infinite or NaN).
*/ */
public double getDouble(int index) throws LuaException { public double getDouble(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (!(value instanceof Number)) throw LuaValues.badArgumentOf(this, index, "number"); if (!(value instanceof Number number)) throw LuaValues.badArgumentOf(this, index, "number");
return ((Number) value).doubleValue(); return number.doubleValue();
} }
/** /**
@ -108,9 +112,9 @@ public abstract class IArguments {
* @throws LuaException If the value is not a long. * @throws LuaException If the value is not a long.
*/ */
public long getLong(int index) throws LuaException { public long getLong(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (!(value instanceof Number)) throw LuaValues.badArgumentOf(this, index, "number"); if (!(value instanceof Number number)) throw LuaValues.badArgumentOf(this, index, "number");
return LuaValues.checkFiniteNum(index, (Number) value).longValue(); return LuaValues.checkFiniteNum(index, number).longValue();
} }
/** /**
@ -132,9 +136,9 @@ public abstract class IArguments {
* @throws LuaException If the value is not a boolean. * @throws LuaException If the value is not a boolean.
*/ */
public boolean getBoolean(int index) throws LuaException { public boolean getBoolean(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (!(value instanceof Boolean)) throw LuaValues.badArgumentOf(this, index, "boolean"); if (!(value instanceof Boolean bool)) throw LuaValues.badArgumentOf(this, index, "boolean");
return (Boolean) value; return bool;
} }
/** /**
@ -145,9 +149,9 @@ public abstract class IArguments {
* @throws LuaException If the value is not a string. * @throws LuaException If the value is not a string.
*/ */
public String getString(int index) throws LuaException { public String getString(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (!(value instanceof String)) throw LuaValues.badArgumentOf(this, index, "string"); if (!(value instanceof String string)) throw LuaValues.badArgumentOf(this, index, "string");
return (String) value; return string;
} }
/** /**
@ -167,12 +171,12 @@ public abstract class IArguments {
* @see Coerced * @see Coerced
*/ */
public String getStringCoerced(int index) throws LuaException { public String getStringCoerced(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (value == null) return "nil"; if (value == null) return "nil";
if (value instanceof Boolean || value instanceof String) return value.toString(); if (value instanceof Boolean || value instanceof String) return value.toString();
if (value instanceof Number) { if (value instanceof Number number) {
double asDouble = ((Number) value).doubleValue(); var asDouble = number.doubleValue();
int asInt = (int) asDouble; var asInt = (int) asDouble;
return asInt == asDouble ? Integer.toString(asInt) : Double.toString(asDouble); return asInt == asDouble ? Integer.toString(asInt) : Double.toString(asDouble);
} }
@ -212,7 +216,7 @@ public abstract class IArguments {
* @throws LuaException If the value is not a table. * @throws LuaException If the value is not a table.
*/ */
public Map<?, ?> getTable(int index) throws LuaException { public Map<?, ?> getTable(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (!(value instanceof Map)) throw LuaValues.badArgumentOf(this, index, "table"); if (!(value instanceof Map)) throw LuaValues.badArgumentOf(this, index, "table");
return (Map<?, ?>) value; return (Map<?, ?>) value;
} }
@ -225,10 +229,10 @@ public abstract class IArguments {
* @throws LuaException If the value is not a number. * @throws LuaException If the value is not a number.
*/ */
public Optional<Double> optDouble(int index) throws LuaException { public Optional<Double> optDouble(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (value == null) return Optional.empty(); if (value == null) return Optional.empty();
if (!(value instanceof Number)) throw LuaValues.badArgumentOf(this, index, "number"); if (!(value instanceof Number number)) throw LuaValues.badArgumentOf(this, index, "number");
return Optional.of(((Number) value).doubleValue()); return Optional.of(number.doubleValue());
} }
/** /**
@ -250,10 +254,10 @@ public abstract class IArguments {
* @throws LuaException If the value is not a number. * @throws LuaException If the value is not a number.
*/ */
public Optional<Long> optLong(int index) throws LuaException { public Optional<Long> optLong(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (value == null) return Optional.empty(); if (value == null) return Optional.empty();
if (!(value instanceof Number)) throw LuaValues.badArgumentOf(this, index, "number"); if (!(value instanceof Number number)) throw LuaValues.badArgumentOf(this, index, "number");
return Optional.of(LuaValues.checkFiniteNum(index, (Number) value).longValue()); return Optional.of(LuaValues.checkFiniteNum(index, number).longValue());
} }
/** /**
@ -264,7 +268,7 @@ public abstract class IArguments {
* @throws LuaException If the value is not finite. * @throws LuaException If the value is not finite.
*/ */
public Optional<Double> optFiniteDouble(int index) throws LuaException { public Optional<Double> optFiniteDouble(int index) throws LuaException {
Optional<Double> value = optDouble(index); var value = optDouble(index);
if (value.isPresent()) LuaValues.checkFiniteNum(index, value.get()); if (value.isPresent()) LuaValues.checkFiniteNum(index, value.get());
return value; return value;
} }
@ -277,10 +281,10 @@ public abstract class IArguments {
* @throws LuaException If the value is not a boolean. * @throws LuaException If the value is not a boolean.
*/ */
public Optional<Boolean> optBoolean(int index) throws LuaException { public Optional<Boolean> optBoolean(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (value == null) return Optional.empty(); if (value == null) return Optional.empty();
if (!(value instanceof Boolean)) throw LuaValues.badArgumentOf(this, index, "boolean"); if (!(value instanceof Boolean bool)) throw LuaValues.badArgumentOf(this, index, "boolean");
return Optional.of((Boolean) value); return Optional.of(bool);
} }
/** /**
@ -291,10 +295,10 @@ public abstract class IArguments {
* @throws LuaException If the value is not a string. * @throws LuaException If the value is not a string.
*/ */
public Optional<String> optString(int index) throws LuaException { public Optional<String> optString(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (value == null) return Optional.empty(); if (value == null) return Optional.empty();
if (!(value instanceof String)) throw LuaValues.badArgumentOf(this, index, "string"); if (!(value instanceof String string)) throw LuaValues.badArgumentOf(this, index, "string");
return Optional.of((String) value); return Optional.of(string);
} }
/** /**
@ -318,7 +322,7 @@ public abstract class IArguments {
* @throws LuaException If the value is not a string or not a valid option for this enum. * @throws LuaException If the value is not a string or not a valid option for this enum.
*/ */
public <T extends Enum<T>> Optional<T> optEnum(int index, Class<T> klass) throws LuaException { public <T extends Enum<T>> Optional<T> optEnum(int index, Class<T> klass) throws LuaException {
Optional<String> str = optString(index); var str = optString(index);
return str.isPresent() ? Optional.of(LuaValues.checkEnum(index, klass, str.get())) : Optional.empty(); return str.isPresent() ? Optional.of(LuaValues.checkEnum(index, klass, str.get())) : Optional.empty();
} }
@ -330,7 +334,7 @@ public abstract class IArguments {
* @throws LuaException If the value is not a table. * @throws LuaException If the value is not a table.
*/ */
public Optional<Map<?, ?>> optTable(int index) throws LuaException { public Optional<Map<?, ?>> optTable(int index) throws LuaException {
Object value = get(index); var value = get(index);
if (value == null) return Optional.empty(); if (value == null) return Optional.empty();
if (!(value instanceof Map)) throw LuaValues.badArgumentOf(this, index, "map"); if (!(value instanceof Map)) throw LuaValues.badArgumentOf(this, index, "map");
return Optional.of((Map<?, ?>) value); return Optional.of((Map<?, ?>) value);
@ -340,7 +344,7 @@ public abstract class IArguments {
* Get an argument as a double. * Get an argument as a double.
* *
* @param index The argument number. * @param index The argument number.
* @param def The public value, if this argument is not given. * @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided. * @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number. * @throws LuaException If the value is not a number.
*/ */
@ -352,7 +356,7 @@ public abstract class IArguments {
* Get an argument as an int. * Get an argument as an int.
* *
* @param index The argument number. * @param index The argument number.
* @param def The public value, if this argument is not given. * @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided. * @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number. * @throws LuaException If the value is not a number.
*/ */
@ -364,7 +368,7 @@ public abstract class IArguments {
* Get an argument as a long. * Get an argument as a long.
* *
* @param index The argument number. * @param index The argument number.
* @param def The public value, if this argument is not given. * @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided. * @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number. * @throws LuaException If the value is not a number.
*/ */
@ -376,7 +380,7 @@ public abstract class IArguments {
* Get an argument as a finite number (not infinite or NaN). * Get an argument as a finite number (not infinite or NaN).
* *
* @param index The argument number. * @param index The argument number.
* @param def The public value, if this argument is not given. * @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided. * @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not finite. * @throws LuaException If the value is not finite.
*/ */
@ -388,7 +392,7 @@ public abstract class IArguments {
* Get an argument as a boolean. * Get an argument as a boolean.
* *
* @param index The argument number. * @param index The argument number.
* @param def The public value, if this argument is not given. * @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided. * @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a boolean. * @throws LuaException If the value is not a boolean.
*/ */
@ -400,11 +404,13 @@ public abstract class IArguments {
* Get an argument as a string. * Get an argument as a string.
* *
* @param index The argument number. * @param index The argument number.
* @param def The public value, if this argument is not given. * @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided. * @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a string. * @throws LuaException If the value is not a string.
*/ */
public String optString(int index, String def) throws LuaException { @Nullable
@Contract("_, !null -> !null")
public String optString(int index, @Nullable String def) throws LuaException {
return optString(index).orElse(def); return optString(index).orElse(def);
} }
@ -412,11 +418,13 @@ public abstract class IArguments {
* Get an argument as a table. * Get an argument as a table.
* *
* @param index The argument number. * @param index The argument number.
* @param def The public value, if this argument is not given. * @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided. * @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a table. * @throws LuaException If the value is not a table.
*/ */
public Map<?, ?> optTable(int index, Map<Object, Object> def) throws LuaException { @Nullable
@Contract("_, !null -> !null")
public Map<?, ?> optTable(int index, @Nullable Map<Object, Object> def) throws LuaException {
return optTable(index).orElse(def); return optTable(index).orElse(def);
} }
@ -437,8 +445,10 @@ public abstract class IArguments {
* *
* @return An {@link IArguments} instance which can escape the current scope. May be {@code this}. * @return An {@link IArguments} instance which can escape the current scope. May be {@code this}.
* @throws LuaException For the same reasons as {@link #get(int)}. * @throws LuaException For the same reasons as {@link #get(int)}.
* @throws IllegalStateException If marking these arguments as escaping outside the scope of the original function.
*/ */
public IArguments escapes() throws LuaException { public IArguments escapes() throws LuaException {
// TODO(1.21.0): Make this return void, require that it mutates this.
return this; return this;
} }
} }

View File

@ -4,21 +4,25 @@
package dan200.computercraft.api.lua; package dan200.computercraft.api.lua;
import javax.annotation.Nullable;
import java.io.Serial;
/** /**
* An exception representing an error in Lua, like that raised by the {@code error()} function. * An exception representing an error in Lua, like that raised by the {@code error()} function.
*/ */
public class LuaException extends Exception { public class LuaException extends Exception {
@Serial
private static final long serialVersionUID = -6136063076818512651L; private static final long serialVersionUID = -6136063076818512651L;
private final boolean hasLevel; private final boolean hasLevel;
private final int level; private final int level;
public LuaException(String message) { public LuaException(@Nullable String message) {
super(message); super(message);
hasLevel = false; hasLevel = false;
level = 1; level = 1;
} }
public LuaException(String message, int level) { public LuaException(@Nullable String message, int level) {
super(message); super(message);
hasLevel = true; hasLevel = true;
this.level = level; this.level = level;

View File

@ -4,6 +4,7 @@
package dan200.computercraft.api.lua; package dan200.computercraft.api.lua;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Map; import java.util.Map;
@ -23,9 +24,9 @@ public final class LuaValues {
* @return The encoded string. * @return The encoded string.
*/ */
public static ByteBuffer encode(String string) { public static ByteBuffer encode(String string) {
byte[] chars = new byte[string.length()]; var chars = new byte[string.length()];
for (int i = 0; i < chars.length; i++) { for (var i = 0; i < chars.length; i++) {
char c = string.charAt(i); var c = string.charAt(i);
chars[i] = c < 256 ? (byte) c : 63; chars[i] = c < 256 ? (byte) c : 63;
} }
@ -53,7 +54,7 @@ public final class LuaValues {
* @return A string representation of the given value's type, in a similar format to that provided by Lua's * @return A string representation of the given value's type, in a similar format to that provided by Lua's
* {@code type} function. * {@code type} function.
*/ */
public static String getType(Object value) { public static String getType(@Nullable Object value) {
if (value == null) return "nil"; if (value == null) return "nil";
if (value instanceof String) return "string"; if (value instanceof String) return "string";
if (value instanceof Boolean) return "boolean"; if (value instanceof Boolean) return "boolean";
@ -147,7 +148,7 @@ public final class LuaValues {
* @throws LuaException If this is not a known enum value. * @throws LuaException If this is not a known enum value.
*/ */
public static <T extends Enum<T>> T checkEnum(int index, Class<T> klass, String value) throws LuaException { public static <T extends Enum<T>> T checkEnum(int index, Class<T> klass, String value) throws LuaException {
for (T possibility : klass.getEnumConstants()) { for (var possibility : klass.getEnumConstants()) {
if (possibility.name().equalsIgnoreCase(value)) return possibility; if (possibility.name().equalsIgnoreCase(value)) return possibility;
} }

View File

@ -4,6 +4,7 @@
package dan200.computercraft.api.lua; package dan200.computercraft.api.lua;
import javax.annotation.Nullable;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -44,6 +45,7 @@ public final class ObjectArguments extends IArguments {
return new ObjectArguments(args.subList(count, args.size())); return new ObjectArguments(args.subList(count, args.size()));
} }
@Nullable
@Override @Override
public Object get(int index) { public Object get(int index) {
return index >= args.size() ? null : args.get(index); return index >= args.size() ? null : args.get(index);

View File

@ -152,6 +152,6 @@ public class FixedWidthFontRenderer {
if (c >= '0' && c <= '9') return c - '0'; if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return 15 - def.ordinal(); return def.ordinal();
} }
} }

View File

@ -371,27 +371,36 @@ public class FSAPI implements ILuaAPI {
public final Object[] open(String path, String mode) throws LuaException { public final Object[] open(String path, String mode) throws LuaException {
try { try {
switch (mode) { switch (mode) {
case "r" -> {
// Open the file for reading, then create a wrapper around the reader // Open the file for reading, then create a wrapper around the reader
case "r":
return new Object[]{ new EncodedReadableHandle(getFileSystem().openForRead(path)) }; return new Object[]{ new EncodedReadableHandle(getFileSystem().openForRead(path)) };
}
case "w" -> {
// Open the file for writing, then create a wrapper around the writer // Open the file for writing, then create a wrapper around the writer
case "w": FileSystemExtensions.makeParentDir(fileSystem, path);
return new Object[]{ new EncodedWritableHandle(getFileSystem().openForWrite(path, false)) }; return new Object[]{ new EncodedWritableHandle(getFileSystem().openForWrite(path, false)) };
}
case "a" -> {
// Open the file for appending, then create a wrapper around the writer // Open the file for appending, then create a wrapper around the writer
case "a": FileSystemExtensions.makeParentDir(fileSystem, path);
return new Object[]{ new EncodedWritableHandle(getFileSystem().openForWrite(path, true)) }; return new Object[]{ new EncodedWritableHandle(getFileSystem().openForWrite(path, true)) };
}
case "rb" -> {
// Open the file for binary reading, then create a wrapper around the reader // Open the file for binary reading, then create a wrapper around the reader
case "rb":
IMountedFileBinary reader = getFileSystem().openForBinaryRead(path); IMountedFileBinary reader = getFileSystem().openForBinaryRead(path);
return new Object[]{ new BinaryReadableHandle(reader) }; return new Object[]{ new BinaryReadableHandle(reader) };
}
case "wb" -> {
// Open the file for binary writing, then create a wrapper around the writer // Open the file for binary writing, then create a wrapper around the writer
case "wb": FileSystemExtensions.makeParentDir(fileSystem, path);
return new Object[]{ new BinaryWritableHandle(getFileSystem().openForBinaryWrite(path, false)) }; return new Object[]{ new BinaryWritableHandle(getFileSystem().openForBinaryWrite(path, false)) };
}
case "ab" -> {
// Open the file for binary appending, then create a wrapper around the reader // Open the file for binary appending, then create a wrapper around the reader
case "ab": FileSystemExtensions.makeParentDir(fileSystem, path);
return new Object[]{ new BinaryWritableHandle(getFileSystem().openForBinaryWrite(path, true)) }; return new Object[]{ new BinaryWritableHandle(getFileSystem().openForBinaryWrite(path, true)) };
default: }
throw new LuaException("Unsupported mode"); default -> throw new LuaException("Unsupported mode");
} }
} catch (FileSystemException e) { } catch (FileSystemException e) {
return new Object[]{ null, e.getMessage() }; return new Object[]{ null, e.getMessage() };
@ -442,29 +451,6 @@ public class FSAPI implements ILuaAPI {
} }
} }
/**
* Searches for files matching a string with wildcards.
* <p>
* This string is formatted like a normal path string, but can include any
* number of wildcards ({@code *}) to look for files matching anything.
* For example, <code>rom/&#42;/command*</code> will look for any path starting with
* {@code command} inside any subdirectory of {@code /rom}.
*
* @param path The wildcard-qualified path to search for.
* @return A list of paths that match the search string.
* @throws LuaException If the path doesn't exist.
* @cc.since 1.6
*/
@LuaFunction
public final String[] find(String path) throws LuaException {
try {
return FileSystemExtensions.find(getFileSystem(), path);
} catch (FileSystemException e) {
throw new LuaException(e.getMessage());
}
}
/** /**
* Returns the capacity of the drive the path is located on. * Returns the capacity of the drive the path is located on.
* *

View File

@ -7,54 +7,15 @@ import com.google.common.base.Splitter;
import dan200.computer.core.FileSystem; import dan200.computer.core.FileSystem;
import dan200.computer.core.FileSystemException; import dan200.computer.core.FileSystemException;
import java.util.*; import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* Backports additional methods from {@link FileSystem}. * Backports additional methods from {@link FileSystem}.
*/ */
final class FileSystemExtensions { final class FileSystemExtensions {
private static void findIn(FileSystem fs, String dir, List<String> matches, Pattern wildPattern) throws FileSystemException {
String[] list = fs.list(dir);
for (String entry : list) {
String entryPath = dir.isEmpty() ? entry : dir + "/" + entry;
if (wildPattern.matcher(entryPath).matches()) {
matches.add(entryPath);
}
if (fs.isDir(entryPath)) {
findIn(fs, entryPath, matches, wildPattern);
}
}
}
public static synchronized String[] find(FileSystem fs, String wildPath) throws FileSystemException {
// Match all the files on the system
wildPath = sanitizePath(wildPath, true);
// If we don't have a wildcard at all just check the file exists
int starIndex = wildPath.indexOf('*');
if (starIndex == -1) {
return fs.exists(wildPath) ? new String[]{ wildPath } : new String[0];
}
// Find the all non-wildcarded directories. For instance foo/bar/baz* -> foo/bar
int prevDir = wildPath.substring(0, starIndex).lastIndexOf('/');
String startDir = prevDir == -1 ? "" : wildPath.substring(0, prevDir);
// If this isn't a directory then just abort
if (!fs.isDir(startDir)) return new String[0];
// Scan as normal, starting from this directory
Pattern wildPattern = Pattern.compile("^\\Q" + wildPath.replaceAll("\\*", "\\\\E[^\\\\/]*\\\\Q") + "\\E$");
List<String> matches = new ArrayList<>();
findIn(fs, startDir, matches, wildPattern);
// Return matches
String[] array = matches.toArray(new String[0]);
Arrays.sort(array);
return array;
}
public static String getDirectory(String path) { public static String getDirectory(String path) {
path = sanitizePath(path, true); path = sanitizePath(path, true);
if (path.isEmpty()) { if (path.isEmpty()) {
@ -69,6 +30,11 @@ final class FileSystemExtensions {
} }
} }
public static void makeParentDir(FileSystem fileSystem, String path) throws FileSystemException {
var parent = getDirectory(path);
if (!parent.isEmpty()) fileSystem.makeDir(parent);
}
private static final Pattern threeDotsPattern = Pattern.compile("^\\.{3,}$"); private static final Pattern threeDotsPattern = Pattern.compile("^\\.{3,}$");
public static String sanitizePath(String path, boolean allowWildcards) { public static String sanitizePath(String path, boolean allowWildcards) {

View File

@ -9,10 +9,10 @@ public final class StringUtil {
} }
public static String normaliseLabel(String label) { public static String normaliseLabel(String label) {
int length = Math.min(32, label.length()); var length = Math.min(32, label.length());
StringBuilder builder = new StringBuilder(length); var builder = new StringBuilder(length);
for (int i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
char c = label.charAt(i); var c = label.charAt(i);
if ((c >= ' ' && c <= '~') || (c >= 161 && c <= 172) || (c >= 174 && c <= 255)) { if ((c >= ' ' && c <= '~') || (c >= 161 && c <= 172) || (c >= 174 && c <= 255)) {
builder.append(c); builder.append(c);
} else { } else {

View File

@ -687,7 +687,7 @@ settings.define("paint.default_extension", {
settings.define("list.show_hidden", { settings.define("list.show_hidden", {
default = false, default = false,
description = [[Show hidden files (those starting with "." in the Lua REPL).]], description = [[Whether the list program show hidden files (those starting with ".").]],
type = "boolean", type = "boolean",
}) })

View File

@ -3,20 +3,20 @@
-- SPDX-License-Identifier: LicenseRef-CCPL -- SPDX-License-Identifier: LicenseRef-CCPL
--[[- Constants and functions for colour values, suitable for working with --[[- Constants and functions for colour values, suitable for working with
@{term} and @{redstone}. [`term`] and [`redstone`].
This is useful in conjunction with @{redstone.setBundledOutput|Bundled Cables} This is useful in conjunction with [Bundled Cables][`redstone.setBundledOutput`]
from mods like Project Red, and @{term.setTextColour|colors on Advanced from mods like Project Red, and [colors on Advanced Computers and Advanced
Computers and Advanced Monitors}. Monitors][`term.setTextColour`].
For the non-American English version just replace @{colors} with @{colours}. For the non-American English version just replace [`colors`] with [`colours`].
This alternative API is exactly the same, except the colours use British English This alternative API is exactly the same, except the colours use British English
(e.g. @{colors.gray} is spelt @{colours.grey}). (e.g. [`colors.gray`] is spelt [`colours.grey`]).
On basic terminals (such as the Computer and Monitor), all the colors are On basic terminals (such as the Computer and Monitor), all the colors are
converted to grayscale. This means you can still use all 16 colors on the converted to grayscale. This means you can still use all 16 colors on the
screen, but they will appear as the nearest tint of gray. You can check if a screen, but they will appear as the nearest tint of gray. You can check if a
terminal supports color by using the function @{term.isColor}. terminal supports color by using the function [`term.isColor`].
Grayscale colors are calculated by taking the average of the three components, Grayscale colors are calculated by taking the average of the three components,
i.e. `(red + green + blue) / 3`. i.e. `(red + green + blue) / 3`.
@ -140,67 +140,67 @@ i.e. `(red + green + blue) / 3`.
local expect = dofile("rom/modules/main/cc/expect.lua").expect local expect = dofile("rom/modules/main/cc/expect.lua").expect
--- White: Written as `0` in paint files and @{term.blit}, has a default --- White: Written as `0` in paint files and [`term.blit`], has a default
-- terminal colour of #F0F0F0. -- terminal colour of #F0F0F0.
white = 0x1 white = 0x1
--- Orange: Written as `1` in paint files and @{term.blit}, has a --- Orange: Written as `1` in paint files and [`term.blit`], has a
-- default terminal colour of #F2B233. -- default terminal colour of #F2B233.
orange = 0x2 orange = 0x2
--- Magenta: Written as `2` in paint files and @{term.blit}, has a --- Magenta: Written as `2` in paint files and [`term.blit`], has a
-- default terminal colour of #E57FD8. -- default terminal colour of #E57FD8.
magenta = 0x4 magenta = 0x4
--- Light blue: Written as `3` in paint files and @{term.blit}, has a --- Light blue: Written as `3` in paint files and [`term.blit`], has a
-- default terminal colour of #99B2F2. -- default terminal colour of #99B2F2.
lightBlue = 0x8 lightBlue = 0x8
--- Yellow: Written as `4` in paint files and @{term.blit}, has a --- Yellow: Written as `4` in paint files and [`term.blit`], has a
-- default terminal colour of #DEDE6C. -- default terminal colour of #DEDE6C.
yellow = 0x10 yellow = 0x10
--- Lime: Written as `5` in paint files and @{term.blit}, has a default --- Lime: Written as `5` in paint files and [`term.blit`], has a default
-- terminal colour of #7FCC19. -- terminal colour of #7FCC19.
lime = 0x20 lime = 0x20
--- Pink: Written as `6` in paint files and @{term.blit}, has a default --- Pink: Written as `6` in paint files and [`term.blit`], has a default
-- terminal colour of #F2B2CC. -- terminal colour of #F2B2CC.
pink = 0x40 pink = 0x40
--- Gray: Written as `7` in paint files and @{term.blit}, has a default --- Gray: Written as `7` in paint files and [`term.blit`], has a default
-- terminal colour of #4C4C4C. -- terminal colour of #4C4C4C.
gray = 0x80 gray = 0x80
--- Light gray: Written as `8` in paint files and @{term.blit}, has a --- Light gray: Written as `8` in paint files and [`term.blit`], has a
-- default terminal colour of #999999. -- default terminal colour of #999999.
lightGray = 0x100 lightGray = 0x100
--- Cyan: Written as `9` in paint files and @{term.blit}, has a default --- Cyan: Written as `9` in paint files and [`term.blit`], has a default
-- terminal colour of #4C99B2. -- terminal colour of #4C99B2.
cyan = 0x200 cyan = 0x200
--- Purple: Written as `a` in paint files and @{term.blit}, has a --- Purple: Written as `a` in paint files and [`term.blit`], has a
-- default terminal colour of #B266E5. -- default terminal colour of #B266E5.
purple = 0x400 purple = 0x400
--- Blue: Written as `b` in paint files and @{term.blit}, has a default --- Blue: Written as `b` in paint files and [`term.blit`], has a default
-- terminal colour of #3366CC. -- terminal colour of #3366CC.
blue = 0x800 blue = 0x800
--- Brown: Written as `c` in paint files and @{term.blit}, has a default --- Brown: Written as `c` in paint files and [`term.blit`], has a default
-- terminal colour of #7F664C. -- terminal colour of #7F664C.
brown = 0x1000 brown = 0x1000
--- Green: Written as `d` in paint files and @{term.blit}, has a default --- Green: Written as `d` in paint files and [`term.blit`], has a default
-- terminal colour of #57A64E. -- terminal colour of #57A64E.
green = 0x2000 green = 0x2000
--- Red: Written as `e` in paint files and @{term.blit}, has a default --- Red: Written as `e` in paint files and [`term.blit`], has a default
-- terminal colour of #CC4C4C. -- terminal colour of #CC4C4C.
red = 0x4000 red = 0x4000
--- Black: Written as `f` in paint files and @{term.blit}, has a default --- Black: Written as `f` in paint files and [`term.blit`], has a default
-- terminal colour of #111111. -- terminal colour of #111111.
black = 0x8000 black = 0x8000
@ -313,18 +313,18 @@ function unpackRGB(rgb)
bit32.band(rgb, 0xFF) / 255 bit32.band(rgb, 0xFF) / 255
end end
--- Either calls @{colors.packRGB} or @{colors.unpackRGB}, depending on how many --- Either calls [`colors.packRGB`] or [`colors.unpackRGB`], depending on how many
-- arguments it receives. -- arguments it receives.
-- --
-- @tparam[1] number r The red channel, as an argument to @{colors.packRGB}. -- @tparam[1] number r The red channel, as an argument to [`colors.packRGB`].
-- @tparam[1] number g The green channel, as an argument to @{colors.packRGB}. -- @tparam[1] number g The green channel, as an argument to [`colors.packRGB`].
-- @tparam[1] number b The blue channel, as an argument to @{colors.packRGB}. -- @tparam[1] number b The blue channel, as an argument to [`colors.packRGB`].
-- @tparam[2] number rgb The combined hexadecimal color, as an argument to @{colors.unpackRGB}. -- @tparam[2] number rgb The combined hexadecimal color, as an argument to [`colors.unpackRGB`].
-- @treturn[1] number The combined hexadecimal colour, as returned by @{colors.packRGB}. -- @treturn[1] number The combined hexadecimal colour, as returned by [`colors.packRGB`].
-- @treturn[2] number The red channel, as returned by @{colors.unpackRGB} -- @treturn[2] number The red channel, as returned by [`colors.unpackRGB`]
-- @treturn[2] number The green channel, as returned by @{colors.unpackRGB} -- @treturn[2] number The green channel, as returned by [`colors.unpackRGB`]
-- @treturn[2] number The blue channel, as returned by @{colors.unpackRGB} -- @treturn[2] number The blue channel, as returned by [`colors.unpackRGB`]
-- @deprecated Use @{packRGB} or @{unpackRGB} directly. -- @deprecated Use [`packRGB`] or [`unpackRGB`] directly.
-- @usage -- @usage
-- ```lua -- ```lua
-- colors.rgb8(0xb23399) -- colors.rgb8(0xb23399)

View File

@ -2,7 +2,7 @@
-- --
-- SPDX-License-Identifier: LicenseRef-CCPL -- SPDX-License-Identifier: LicenseRef-CCPL
--- An alternative version of @{colors} for lovers of British spelling. --- An alternative version of [`colors`] for lovers of British spelling.
-- --
-- @see colors -- @see colors
-- @module colours -- @module colours
@ -13,14 +13,14 @@ for k, v in pairs(colors) do
colours[k] = v colours[k] = v
end end
--- Grey. Written as `7` in paint files and @{term.blit}, has a default --- Grey. Written as `7` in paint files and [`term.blit`], has a default
-- terminal colour of #4C4C4C. -- terminal colour of #4C4C4C.
-- --
-- @see colors.gray -- @see colors.gray
colours.grey = colors.gray colours.grey = colors.gray
colours.gray = nil --- @local colours.gray = nil --- @local
--- Light grey. Written as `8` in paint files and @{term.blit}, has a --- Light grey. Written as `8` in paint files and [`term.blit`], has a
-- default terminal colour of #999999. -- default terminal colour of #999999.
-- --
-- @see colors.lightGray -- @see colors.lightGray

View File

@ -5,20 +5,19 @@
--[[- Execute [Minecraft commands][mc] and gather data from the results from --[[- Execute [Minecraft commands][mc] and gather data from the results from
a command computer. a command computer.
:::note > [!NOTE]
This API is only available on Command computers. It is not accessible to normal > This API is only available on Command computers. It is not accessible to normal
players. > players.
:::
While one may use @{commands.exec} directly to execute a command, the While one may use [`commands.exec`] directly to execute a command, the
commands API also provides helper methods to execute every command. For commands API also provides helper methods to execute every command. For
instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`. instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`.
@{commands.async} provides a similar interface to execute asynchronous [`commands.async`] provides a similar interface to execute asynchronous
commands. `commands.async.say("Hi!")` is equivalent to commands. `commands.async.say("Hi!")` is equivalent to
`commands.execAsync("say Hi!")`. `commands.execAsync("say Hi!")`.
[mc]: https://minecraft.gamepedia.com/Commands [mc]: https://minecraft.wiki/w/Commands
@module commands @module commands
@usage Set the block above this computer to stone: @usage Set the block above this computer to stone:
@ -31,7 +30,7 @@ end
--- The builtin commands API, without any generated command helper functions --- The builtin commands API, without any generated command helper functions
-- --
-- This may be useful if a built-in function (such as @{commands.list}) has been -- This may be useful if a built-in function (such as [`commands.list`]) has been
-- overwritten by a command. -- overwritten by a command.
local native = commands.native or commands local native = commands.native or commands
@ -112,7 +111,7 @@ end
--- A table containing asynchronous wrappers for all commands. --- A table containing asynchronous wrappers for all commands.
-- --
-- As with @{commands.execAsync}, this returns the "task id" of the enqueued -- As with [`commands.execAsync`], this returns the "task id" of the enqueued
-- command. -- command.
-- @see execAsync -- @see execAsync
-- @usage Asynchronously sets the block above the computer to stone. -- @usage Asynchronously sets the block above the computer to stone.

View File

@ -9,10 +9,9 @@ locally attached drive, specify “side” as one of the six sides (e.g. `left`)
use a remote disk drive, specify its name as printed when enabling its modem use a remote disk drive, specify its name as printed when enabling its modem
(e.g. `drive_0`). (e.g. `drive_0`).
:::tip > [!TIP]
All computers (except command computers), turtles and pocket computers can be > All computers (except command computers), turtles and pocket computers can be
placed within a disk drive to access it's internal storage like a disk. > placed within a disk drive to access it's internal storage like a disk.
:::
@module disk @module disk
@since 1.2 @since 1.2
@ -95,9 +94,9 @@ end
--- Whether the current disk is a [music disk][disk] as opposed to a floppy disk --- Whether the current disk is a [music disk][disk] as opposed to a floppy disk
-- or other item. -- or other item.
-- --
-- If this returns true, you will can @{disk.playAudio|play} the record. -- If this returns true, you will can [play][`disk.playAudio`] the record.
-- --
-- [disk]: https://minecraft.gamepedia.com/Music_Disc -- [disk]: https://minecraft.wiki/w/Music_Disc
-- --
-- @tparam string name The name of the disk drive. -- @tparam string name The name of the disk drive.
-- @treturn boolean If the disk is present and has audio saved on it. -- @treturn boolean If the disk is present and has audio saved on it.
@ -110,10 +109,10 @@ end
--- Get the title of the audio track from the music record in the drive. --- Get the title of the audio track from the music record in the drive.
-- --
-- This generally returns the same as @{disk.getLabel} for records. -- This generally returns the same as [`disk.getLabel`] for records.
-- --
-- @tparam string name The name of the disk drive. -- @tparam string name The name of the disk drive.
-- @treturn string|false|nil The track title, @{false} if there is not a music -- @treturn string|false|nil The track title, [`false`] if there is not a music
-- record in the drive or `nil` if no drive is present. -- record in the drive or `nil` if no drive is present.
function getAudioTitle(name) function getAudioTitle(name)
if isDrive(name) then if isDrive(name) then
@ -126,7 +125,7 @@ end
-- --
-- If any record is already playing on any disk drive, it stops before the -- If any record is already playing on any disk drive, it stops before the
-- target drive starts playing. The record stops when it reaches the end of the -- target drive starts playing. The record stops when it reaches the end of the
-- track, when it is removed from the drive, when @{disk.stopAudio} is called, or -- track, when it is removed from the drive, when [`disk.stopAudio`] is called, or
-- when another record is started. -- when another record is started.
-- --
-- @tparam string name The name of the disk drive. -- @tparam string name The name of the disk drive.
@ -138,7 +137,7 @@ function playAudio(name)
end end
--- Stops the music record in the drive from playing, if it was started with --- Stops the music record in the drive from playing, if it was started with
-- @{disk.playAudio}. -- [`disk.playAudio`].
-- --
-- @tparam string name The name o the disk drive. -- @tparam string name The name o the disk drive.
function stopAudio(name) function stopAudio(name)
@ -165,7 +164,7 @@ end
--- Returns a number which uniquely identifies the disk in the drive. --- Returns a number which uniquely identifies the disk in the drive.
-- --
-- Note, unlike @{disk.getLabel}, this does not return anything for other media, -- Note, unlike [`disk.getLabel`], this does not return anything for other media,
-- such as computers or turtles. -- such as computers or turtles.
-- --
-- @tparam string name The name of the disk drive. -- @tparam string name The name of the disk drive.

View File

@ -13,19 +13,19 @@ local fs = _ENV
for k, v in pairs(native) do fs[k] = v end for k, v in pairs(native) do fs[k] = v end
--[[- Provides completion for a file or directory name, suitable for use with --[[- Provides completion for a file or directory name, suitable for use with
@{_G.read}. [`_G.read`].
When a directory is a possible candidate for completion, two entries are When a directory is a possible candidate for completion, two entries are
included - one with a trailing slash (indicating that entries within this included - one with a trailing slash (indicating that entries within this
directory exist) and one without it (meaning this entry is an immediate directory exist) and one without it (meaning this entry is an immediate
completion candidate). `include_dirs` can be set to @{false} to only include completion candidate). `include_dirs` can be set to [`false`] to only include
those with a trailing slash. those with a trailing slash.
@tparam[1] string path The path to complete. @tparam[1] string path The path to complete.
@tparam[1] string location The location where paths are resolved from. @tparam[1] string location The location where paths are resolved from.
@tparam[1,opt=true] boolean include_files When @{false}, only directories will @tparam[1,opt=true] boolean include_files When [`false`], only directories will
be included in the returned list. be included in the returned list.
@tparam[1,opt=true] boolean include_dirs When @{false}, "raw" directories will @tparam[1,opt=true] boolean include_dirs When [`false`], "raw" directories will
not be included in the returned list. not be included in the returned list.
@tparam[2] string path The path to complete. @tparam[2] string path The path to complete.
@ -133,6 +133,93 @@ function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
return {} return {}
end end
local function find_aux(path, parts, i, out)
local part = parts[i]
if not part then
-- If we're at the end of the pattern, ensure our path exists and append it.
if fs.exists(path) then out[#out + 1] = path end
elseif part.exact then
-- If we're an exact match, just recurse into this directory.
return find_aux(fs.combine(path, part.contents), parts, i + 1, out)
else
-- Otherwise we're a pattern. Check we're a directory, then recurse into each
-- matching file.
if not fs.isDir(path) then return end
local files = fs.list(path)
for j = 1, #files do
local file = files[j]
if file:find(part.contents) then find_aux(fs.combine(path, file), parts, i + 1, out) end
end
end
end
local find_escape = {
-- Escape standard Lua pattern characters
["^"] = "%^", ["$"] = "%$", ["("] = "%(", [")"] = "%)", ["%"] = "%%",
["."] = "%.", ["["] = "%[", ["]"] = "%]", ["+"] = "%+", ["-"] = "%-",
-- Aside from our wildcards.
["*"] = ".*",
["?"] = ".",
}
--[[- Searches for files matching a string with wildcards.
This string looks like a normal path string, but can include wildcards, which
can match multiple paths:
- "?" matches any single character in a file name.
- "*" matches any number of characters.
For example, `rom/*/command*` will look for any path starting with `command`
inside any subdirectory of `/rom`.
Note that these wildcards match a single segment of the path. For instance
`rom/*.lua` will include `rom/startup.lua` but _not_ include `rom/programs/list.lua`.
@tparam string path The wildcard-qualified path to search for.
@treturn { string... } A list of paths that match the search string.
@throws If the supplied path was invalid.
@since 1.6
@changed 1.106.0 Added support for the `?` wildcard.
@usage List all Markdown files in the help folder
fs.find("rom/help/*.md")
]]
function fs.find(pattern)
expect(1, pattern, "string")
pattern = fs.combine(pattern) -- Normalise the path, removing ".."s.
-- If the pattern is trying to search outside the computer root, just abort.
-- This will fail later on anyway.
if pattern == ".." or pattern:sub(1, 3) == "../" then
error("/" .. pattern .. ": Invalid Path", 2)
end
-- If we've no wildcards, just check the file exists.
if not pattern:find("[*?]") then
if fs.exists(pattern) then return { pattern } else return {} end
end
local parts = {}
for part in pattern:gmatch("[^/]+") do
if part:find("[*?]") then
parts[#parts + 1] = {
exact = false,
contents = "^" .. part:gsub(".", find_escape) .. "$",
}
else
parts[#parts + 1] = { exact = true, contents = part }
end
end
local out = {}
find_aux("", parts, 1, out)
return out
end
--- Returns true if a path is mounted to the parent filesystem. --- Returns true if a path is mounted to the parent filesystem.
-- --
-- The root filesystem "/" is considered a mount, along with disk folders and -- The root filesystem "/" is considered a mount, along with disk folders and

View File

@ -2,21 +2,20 @@
-- --
-- SPDX-License-Identifier: LicenseRef-CCPL -- SPDX-License-Identifier: LicenseRef-CCPL
--[[- Use @{modem|modems} to locate the position of the current turtle or --[[- Use [modems][`modem`] to locate the position of the current turtle or
computers. computers.
It broadcasts a PING message over @{rednet} and wait for responses. In order for It broadcasts a PING message over [`rednet`] and wait for responses. In order for
this system to work, there must be at least 4 computers used as gps hosts which this system to work, there must be at least 4 computers used as gps hosts which
will respond and allow trilateration. Three of these hosts should be in a plane, will respond and allow trilateration. Three of these hosts should be in a plane,
and the fourth should be either above or below the other three. The three in a and the fourth should be either above or below the other three. The three in a
plane should not be in a line with each other. You can set up hosts using the plane should not be in a line with each other. You can set up hosts using the
gps program. gps program.
:::note > [!NOTE]
When entering in the coordinates for the host you need to put in the `x`, `y`, > When entering in the coordinates for the host you need to put in the `x`, `y`,
and `z` coordinates of the block that the modem is connected to, not the modem. > and `z` coordinates of the block that the modem is connected to, not the modem.
All modem distances are measured from the block that the modem is placed on. > All modem distances are measured from the block that the modem is placed on.
:::
Also note that you may choose which axes x, y, or z refers to - so long as your Also note that you may choose which axes x, y, or z refers to - so long as your
systems have the same definition as any GPS servers that're in range, it works systems have the same definition as any GPS servers that're in range, it works

View File

@ -74,10 +74,10 @@ handleMetatable = {
This can be used in a for loop to iterate over all lines of a file This can be used in a for loop to iterate over all lines of a file
Once the end of the file has been reached, @{nil} will be returned. The file is Once the end of the file has been reached, [`nil`] will be returned. The file is
*not* automatically closed. *not* automatically closed.
@param ... The argument to pass to @{Handle:read} for each line. @param ... The argument to pass to [`Handle:read`] for each line.
@treturn function():string|nil The line iterator. @treturn function():string|nil The line iterator.
@throws If the file cannot be opened for reading @throws If the file cannot be opened for reading
@since 1.3 @since 1.3
@ -324,14 +324,14 @@ each time it is called, returns a new line from the file.
This can be used in a for loop to iterate over all lines of a file This can be used in a for loop to iterate over all lines of a file
Once the end of the file has been reached, @{nil} will be returned. The file is Once the end of the file has been reached, [`nil`] will be returned. The file is
automatically closed. automatically closed.
If no file name is given, the @{io.input|current input} will be used instead. If no file name is given, the [current input][`io.input`] will be used instead.
In this case, the handle is not used. In this case, the handle is not used.
@tparam[opt] string filename The name of the file to extract lines from @tparam[opt] string filename The name of the file to extract lines from
@param ... The argument to pass to @{Handle:read} for each line. @param ... The argument to pass to [`Handle:read`] for each line.
@treturn function():string|nil The line iterator. @treturn function():string|nil The line iterator.
@throws If the file cannot be opened for reading @throws If the file cannot be opened for reading
@ -362,7 +362,7 @@ function lines(filename, ...)
end end
--- Open a file with the given mode, either returning a new file handle --- Open a file with the given mode, either returning a new file handle
-- or @{nil}, plus an error message. -- or [`nil`], plus an error message.
-- --
-- The `mode` string can be any of the following: -- The `mode` string can be any of the following:
-- - **"r"**: Read mode -- - **"r"**: Read mode
@ -410,11 +410,11 @@ end
--- Read from the currently opened input file. --- Read from the currently opened input file.
-- --
-- This is equivalent to `io.input():read(...)`. See @{Handle:read|the -- This is equivalent to `io.input():read(...)`. See [the documentation][`Handle:read`]
-- documentation} there for full details. -- there for full details.
-- --
-- @tparam string ... The formats to read, defaulting to a whole line. -- @tparam string ... The formats to read, defaulting to a whole line.
-- @treturn (string|nil)... The data read, or @{nil} if nothing can be read. -- @treturn (string|nil)... The data read, or [`nil`] if nothing can be read.
function read(...) function read(...)
return currentInput:read(...) return currentInput:read(...)
end end
@ -438,8 +438,8 @@ end
--- Write to the currently opened output file. --- Write to the currently opened output file.
-- --
-- This is equivalent to `io.output():write(...)`. See @{Handle:write|the -- This is equivalent to `io.output():write(...)`. See [the documentation][`Handle:write`]
-- documentation} there for full details. -- there for full details.
-- --
-- @tparam string ... The strings to write -- @tparam string ... The strings to write
-- @changed 1.81.0 Multiple arguments are now allowed. -- @changed 1.81.0 Multiple arguments are now allowed.

View File

@ -51,7 +51,7 @@ end
-- --
-- @tparam string image The string containing the raw-image data. -- @tparam string image The string containing the raw-image data.
-- @treturn table The parsed image data, suitable for use with -- @treturn table The parsed image data, suitable for use with
-- @{paintutils.drawImage}. -- [`paintutils.drawImage`].
-- @since 1.80pr1 -- @since 1.80pr1
function parseImage(image) function parseImage(image)
expect(1, image, "string") expect(1, image, "string")
@ -69,7 +69,7 @@ end
-- @tparam string path The file to load. -- @tparam string path The file to load.
-- --
-- @treturn table|nil The parsed image data, suitable for use with -- @treturn table|nil The parsed image data, suitable for use with
-- @{paintutils.drawImage}, or `nil` if the file does not exist. -- [`paintutils.drawImage`], or `nil` if the file does not exist.
-- @usage Load an image and draw it. -- @usage Load an image and draw it.
-- --
-- local image = paintutils.loadImage("data/example.nfp") -- local image = paintutils.loadImage("data/example.nfp")
@ -93,7 +93,7 @@ end
-- --
-- @tparam number xPos The x position to draw at, where 1 is the far left. -- @tparam number xPos The x position to draw at, where 1 is the far left.
-- @tparam number yPos The y position to draw at, where 1 is the very top. -- @tparam number yPos The y position to draw at, where 1 is the very top.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be -- @tparam[opt] number colour The [color][`colors`] of this pixel. This will be
-- the current background colour if not specified. -- the current background colour if not specified.
function drawPixel(xPos, yPos, colour) function drawPixel(xPos, yPos, colour)
expect(1, xPos, "number") expect(1, xPos, "number")
@ -115,7 +115,7 @@ end
-- @tparam number startY The starting y position of the line. -- @tparam number startY The starting y position of the line.
-- @tparam number endX The end x position of the line. -- @tparam number endX The end x position of the line.
-- @tparam number endY The end y position of the line. -- @tparam number endY The end y position of the line.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be -- @tparam[opt] number colour The [color][`colors`] of this pixel. This will be
-- the current background colour if not specified. -- the current background colour if not specified.
-- @usage paintutils.drawLine(2, 3, 30, 7, colors.red) -- @usage paintutils.drawLine(2, 3, 30, 7, colors.red)
function drawLine(startX, startY, endX, endY, colour) function drawLine(startX, startY, endX, endY, colour)
@ -189,7 +189,7 @@ end
-- @tparam number startY The starting y position of the line. -- @tparam number startY The starting y position of the line.
-- @tparam number endX The end x position of the line. -- @tparam number endX The end x position of the line.
-- @tparam number endY The end y position of the line. -- @tparam number endY The end y position of the line.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be -- @tparam[opt] number colour The [color][`colors`] of this pixel. This will be
-- the current background colour if not specified. -- the current background colour if not specified.
-- @usage paintutils.drawBox(2, 3, 30, 7, colors.red) -- @usage paintutils.drawBox(2, 3, 30, 7, colors.red)
function drawBox(startX, startY, endX, endY, nColour) function drawBox(startX, startY, endX, endY, nColour)
@ -242,7 +242,7 @@ end
-- @tparam number startY The starting y position of the line. -- @tparam number startY The starting y position of the line.
-- @tparam number endX The end x position of the line. -- @tparam number endX The end x position of the line.
-- @tparam number endY The end y position of the line. -- @tparam number endY The end y position of the line.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be -- @tparam[opt] number colour The [color][`colors`] of this pixel. This will be
-- the current background colour if not specified. -- the current background colour if not specified.
-- @usage paintutils.drawFilledBox(2, 3, 30, 7, colors.red) -- @usage paintutils.drawFilledBox(2, 3, 30, 7, colors.red)
function drawFilledBox(startX, startY, endX, endY, nColour) function drawFilledBox(startX, startY, endX, endY, nColour)
@ -278,7 +278,7 @@ function drawFilledBox(startX, startY, endX, endY, nColour)
end end
end end
--- Draw an image loaded by @{paintutils.parseImage} or @{paintutils.loadImage}. --- Draw an image loaded by [`paintutils.parseImage`] or [`paintutils.loadImage`].
-- --
-- @tparam table image The parsed image data. -- @tparam table image The parsed image data.
-- @tparam number xPos The x position to start drawing at. -- @tparam number xPos The x position to start drawing at.

View File

@ -6,36 +6,34 @@
Functions are not actually executed simultaneously, but rather this API will Functions are not actually executed simultaneously, but rather this API will
automatically switch between them whenever they yield (e.g. whenever they call automatically switch between them whenever they yield (e.g. whenever they call
@{coroutine.yield}, or functions that call that - such as @{os.pullEvent} - or [`coroutine.yield`], or functions that call that - such as [`os.pullEvent`] - or
functions that call that, etc - basically, anything that causes the function functions that call that, etc - basically, anything that causes the function
to "pause"). to "pause").
Each function executed in "parallel" gets its own copy of the event queue, Each function executed in "parallel" gets its own copy of the event queue,
and so "event consuming" functions (again, mostly anything that causes the and so "event consuming" functions (again, mostly anything that causes the
script to pause - eg @{os.sleep}, @{rednet.receive}, most of the @{turtle} API, script to pause - eg [`os.sleep`], [`rednet.receive`], most of the [`turtle`] API,
etc) can safely be used in one without affecting the event queue accessed by etc) can safely be used in one without affecting the event queue accessed by
the other. the other.
:::caution > [!WARNING]
When using this API, be careful to pass the functions you want to run in > When using this API, be careful to pass the functions you want to run in
parallel, and _not_ the result of calling those functions. > parallel, and _not_ the result of calling those functions.
>
For instance, the following is correct: > For instance, the following is correct:
>
```lua > ```lua
local function do_sleep() sleep(1) end > local function do_sleep() sleep(1) end
parallel.waitForAny(do_sleep, rednet.receive) > parallel.waitForAny(do_sleep, rednet.receive)
``` > ```
>
but the following is **NOT**: > but the following is **NOT**:
>
```lua > ```lua
local function do_sleep() sleep(1) end > local function do_sleep() sleep(1) end
parallel.waitForAny(do_sleep(), rednet.receive) > parallel.waitForAny(do_sleep(), rednet.receive)
``` > ```
:::
@module parallel @module parallel
@since 1.2 @since 1.2
@ -100,7 +98,7 @@ end
--[[- Switches between execution of the functions, until any of them --[[- Switches between execution of the functions, until any of them
finishes. If any of the functions errors, the message is propagated upwards finishes. If any of the functions errors, the message is propagated upwards
from the @{parallel.waitForAny} call. from the [`parallel.waitForAny`] call.
@tparam function ... The functions this task will run @tparam function ... The functions this task will run
@usage Print a message every second until the `q` key is pressed. @usage Print a message every second until the `q` key is pressed.
@ -128,7 +126,7 @@ end
--[[- Switches between execution of the functions, until all of them are --[[- Switches between execution of the functions, until all of them are
finished. If any of the functions errors, the message is propagated upwards finished. If any of the functions errors, the message is propagated upwards
from the @{parallel.waitForAll} call. from the [`parallel.waitForAll`] call.
@tparam function ... The functions this task will run @tparam function ... The functions this task will run
@usage Start off two timers and wait for them both to run. @usage Start off two timers and wait for them both to run.

View File

@ -5,8 +5,8 @@
--[[- Find and control peripherals attached to this computer. --[[- Find and control peripherals attached to this computer.
Peripherals are blocks (or turtle and pocket computer upgrades) which can Peripherals are blocks (or turtle and pocket computer upgrades) which can
be controlled by a computer. For instance, the @{speaker} peripheral allows a be controlled by a computer. For instance, the [`speaker`] peripheral allows a
computer to play music and the @{monitor} peripheral allows you to display text computer to play music and the [`monitor`] peripheral allows you to display text
in the world. in the world.
## Referencing peripherals ## Referencing peripherals
@ -18,10 +18,10 @@ computer will be called `"bottom"` in your Lua code, one to the left called
`"right"`, `"front"`, `"back"`). `"right"`, `"front"`, `"back"`).
You can list the names of all peripherals with the `peripherals` program, or the You can list the names of all peripherals with the `peripherals` program, or the
@{peripheral.getNames} function. [`peripheral.getNames`] function.
It's also possible to use peripherals which are further away from your computer It's also possible to use peripherals which are further away from your computer
through the use of @{modem|Wired Modems}. Place one modem against your computer through the use of [Wired Modems][`modem`]. Place one modem against your computer
(you may need to sneak and right click), run Networking Cable to your (you may need to sneak and right click), run Networking Cable to your
peripheral, and then place another modem against that block. You can then right peripheral, and then place another modem against that block. You can then right
click the modem to use (or *attach*) the peripheral. This will print a click the modem to use (or *attach*) the peripheral. This will print a
@ -32,24 +32,23 @@ clipboard.
## Using peripherals ## Using peripherals
Once you have the name of a peripheral, you can call functions on it using the Once you have the name of a peripheral, you can call functions on it using the
@{peripheral.call} function. This takes the name of our peripheral, the name of [`peripheral.call`] function. This takes the name of our peripheral, the name of
the function we want to call, and then its arguments. the function we want to call, and then its arguments.
:::info > [!INFO]
Some bits of the peripheral API call peripheral functions *methods* instead > Some bits of the peripheral API call peripheral functions *methods* instead
(for example, the @{peripheral.getMethods} function). Don't worry, they're the > (for example, the [`peripheral.getMethods`] function). Don't worry, they're the
same thing! > same thing!
:::
Let's say we have a monitor above our computer (and so "top") and want to Let's say we have a monitor above our computer (and so "top") and want to
@{monitor.write|write some text to it}. We'd write the following: [write some text to it][`monitor.write`]. We'd write the following:
```lua ```lua
peripheral.call("top", "write", "This is displayed on a monitor!") peripheral.call("top", "write", "This is displayed on a monitor!")
``` ```
Once you start calling making a couple of peripheral calls this can get very Once you start calling making a couple of peripheral calls this can get very
repetitive, and so we can @{peripheral.wrap|wrap} a peripheral. This builds a repetitive, and so we can [wrap][`peripheral.wrap`] a peripheral. This builds a
table of all the peripheral's functions so you can use it like an API or module. table of all the peripheral's functions so you can use it like an API or module.
For instance, we could have written the above example as follows: For instance, we could have written the above example as follows:
@ -66,7 +65,7 @@ called, you just need to know it's there. For instance, if you're writing a
music player, you just need a speaker - it doesn't matter if it's above or below music player, you just need a speaker - it doesn't matter if it's above or below
the computer. the computer.
Thankfully there's a quick way to do this: @{peripheral.find}. This takes a Thankfully there's a quick way to do this: [`peripheral.find`]. This takes a
*peripheral type* and returns all the attached peripherals which are of this *peripheral type* and returns all the attached peripherals which are of this
type. type.
@ -76,10 +75,10 @@ are just called `"speaker"`, and monitors `"monitor"`. Some peripherals might
have more than one type - a Minecraft chest is both a `"minecraft:chest"` and have more than one type - a Minecraft chest is both a `"minecraft:chest"` and
`"inventory"`. `"inventory"`.
You can get all the types a peripheral has with @{peripheral.getType}, and check You can get all the types a peripheral has with [`peripheral.getType`], and check
a peripheral is a specific type with @{peripheral.hasType}. a peripheral is a specific type with [`peripheral.hasType`].
To return to our original example, let's use @{peripheral.find} to find an To return to our original example, let's use [`peripheral.find`] to find an
attached speaker: attached speaker:
```lua ```lua
@ -233,7 +232,7 @@ function getMethods(name)
return nil return nil
end end
--- Get the name of a peripheral wrapped with @{peripheral.wrap}. --- Get the name of a peripheral wrapped with [`peripheral.wrap`].
-- --
-- @tparam table peripheral The peripheral to get the name of. -- @tparam table peripheral The peripheral to get the name of.
-- @treturn string The name of the given peripheral. -- @treturn string The name of the given peripheral.
@ -274,7 +273,7 @@ function call(name, method, ...)
end end
--- Get a table containing all functions available on a peripheral. These can --- Get a table containing all functions available on a peripheral. These can
-- then be called instead of using @{peripheral.call} every time. -- then be called instead of using [`peripheral.call`] every time.
-- --
-- @tparam string name The name of the peripheral to wrap. -- @tparam string name The name of the peripheral to wrap.
-- @treturn table|nil The table containing the peripheral's methods, or `nil` if -- @treturn table|nil The table containing the peripheral's methods, or `nil` if
@ -309,7 +308,7 @@ function wrap(name)
end end
--[[- Find all peripherals of a specific type, and return the --[[- Find all peripherals of a specific type, and return the
@{peripheral.wrap|wrapped} peripherals. [wrapped][`peripheral.wrap`] peripherals.
@tparam string ty The type of peripheral to look for. @tparam string ty The type of peripheral to look for.
@tparam[opt] function(name:string, wrapped:table):boolean filter A @tparam[opt] function(name:string, wrapped:table):boolean filter A
@ -329,7 +328,7 @@ and returns if it should be included in the result.
return modem.isWireless() -- Check this modem is wireless. return modem.isWireless() -- Check this modem is wireless.
end) } end) }
@usage This abuses the `filter` argument to call @{rednet.open} on every modem. @usage This abuses the `filter` argument to call [`rednet.open`] on every modem.
peripheral.find("modem", rednet.open) peripheral.find("modem", rednet.open)
@since 1.6 @since 1.6

View File

@ -2,42 +2,41 @@
-- --
-- SPDX-License-Identifier: LicenseRef-CCPL -- SPDX-License-Identifier: LicenseRef-CCPL
--[[- Communicate with other computers by using @{modem|modems}. @{rednet} --[[- Communicate with other computers by using [modems][`modem`]. [`rednet`]
provides a layer of abstraction on top of the main @{modem} peripheral, making provides a layer of abstraction on top of the main [`modem`] peripheral, making
it slightly easier to use. it slightly easier to use.
## Basic usage ## Basic usage
In order to send a message between two computers, each computer must have a In order to send a message between two computers, each computer must have a
modem on one of its sides (or in the case of pocket computers and turtles, the modem on one of its sides (or in the case of pocket computers and turtles, the
modem must be equipped as an upgrade). The two computers should then call modem must be equipped as an upgrade). The two computers should then call
@{rednet.open}, which sets up the modems ready to send and receive messages. [`rednet.open`], which sets up the modems ready to send and receive messages.
Once rednet is opened, you can send messages using @{rednet.send} and receive Once rednet is opened, you can send messages using [`rednet.send`] and receive
them using @{rednet.receive}. It's also possible to send a message to _every_ them using [`rednet.receive`]. It's also possible to send a message to _every_
rednet-using computer using @{rednet.broadcast}. rednet-using computer using [`rednet.broadcast`].
:::caution Network security > [Network security][!WARNING]
>
While rednet provides a friendly way to send messages to specific computers, it > While rednet provides a friendly way to send messages to specific computers, it
doesn't provide any guarantees about security. Other computers could be > doesn't provide any guarantees about security. Other computers could be
listening in to your messages, or even pretending to send messages from other computers! > listening in to your messages, or even pretending to send messages from other computers!
>
If you're playing on a multi-player server (or at least one where you don't > If you're playing on a multi-player server (or at least one where you don't
trust other players), it's worth encrypting or signing your rednet messages. > trust other players), it's worth encrypting or signing your rednet messages.
:::
## Protocols and hostnames ## Protocols and hostnames
Several rednet messages accept "protocol"s - simple string names describing what Several rednet messages accept "protocol"s - simple string names describing what
a message is about. When sending messages using @{rednet.send} and a message is about. When sending messages using [`rednet.send`] and
@{rednet.broadcast}, you can optionally specify a protocol for the message. This [`rednet.broadcast`], you can optionally specify a protocol for the message. This
same protocol can then be given to @{rednet.receive}, to ignore all messages not same protocol can then be given to [`rednet.receive`], to ignore all messages not
using this protocol. using this protocol.
It's also possible to look-up computers based on protocols, providing a basic It's also possible to look-up computers based on protocols, providing a basic
system for service discovery and [DNS]. A computer can advertise that it system for service discovery and [DNS]. A computer can advertise that it
supports a particular protocol with @{rednet.host}, also providing a friendly supports a particular protocol with [`rednet.host`], also providing a friendly
"hostname". Other computers may then find all computers which support this "hostname". Other computers may then find all computers which support this
protocol using @{rednet.lookup}. protocol using [`rednet.lookup`].
[DNS]: https://en.wikipedia.org/wiki/Domain_Name_System "Domain Name System" [DNS]: https://en.wikipedia.org/wiki/Domain_Name_System "Domain Name System"
@ -50,7 +49,7 @@ bare-bones but flexible interface.
local expect = dofile("rom/modules/main/cc/expect.lua").expect local expect = dofile("rom/modules/main/cc/expect.lua").expect
--- The channel used by the Rednet API to @{broadcast} messages. --- The channel used by the Rednet API to [`broadcast`] messages.
CHANNEL_BROADCAST = 65535 CHANNEL_BROADCAST = 65535
--- The channel used by the Rednet API to repeat messages. --- The channel used by the Rednet API to repeat messages.
@ -68,12 +67,12 @@ local function id_as_channel(id)
return (id or os.getComputerID()) % MAX_ID_CHANNELS return (id or os.getComputerID()) % MAX_ID_CHANNELS
end end
--[[- Opens a modem with the given @{peripheral} name, allowing it to send and --[[- Opens a modem with the given [`peripheral`] name, allowing it to send and
receive messages over rednet. receive messages over rednet.
This will open the modem on two channels: one which has the same This will open the modem on two channels: one which has the same
@{os.getComputerID|ID} as the computer, and another on [ID][`os.getComputerID`] as the computer, and another on
@{CHANNEL_BROADCAST|the broadcast channel}. [the broadcast channel][`CHANNEL_BROADCAST`].
@tparam string modem The name of the modem to open. @tparam string modem The name of the modem to open.
@throws If there is no such modem with the given name @throws If there is no such modem with the given name
@ -83,7 +82,7 @@ rednet messages using it.
rednet.open("back") rednet.open("back")
@usage Open rednet on all attached modems. This abuses the "filter" argument to @usage Open rednet on all attached modems. This abuses the "filter" argument to
@{peripheral.find}. [`peripheral.find`].
peripheral.find("modem", rednet.open) peripheral.find("modem", rednet.open)
@see rednet.close @see rednet.close
@ -98,7 +97,7 @@ function open(modem)
peripheral.call(modem, "open", CHANNEL_BROADCAST) peripheral.call(modem, "open", CHANNEL_BROADCAST)
end end
--- Close a modem with the given @{peripheral} name, meaning it can no longer --- Close a modem with the given [`peripheral`] name, meaning it can no longer
-- send and receive rednet messages. -- send and receive rednet messages.
-- --
-- @tparam[opt] string modem The side the modem exists on. If not given, all -- @tparam[opt] string modem The side the modem exists on. If not given, all
@ -151,21 +150,21 @@ end
--[[- Allows a computer or turtle with an attached modem to send a message --[[- Allows a computer or turtle with an attached modem to send a message
intended for a sycomputer with a specific ID. At least one such modem must first intended for a sycomputer with a specific ID. At least one such modem must first
be @{rednet.open|opened} before sending is possible. be [opened][`rednet.open`] before sending is possible.
Assuming the target was in range and also had a correctly opened modem, the Assuming the target was in range and also had a correctly opened modem, the
target computer may then use @{rednet.receive} to collect the message. target computer may then use [`rednet.receive`] to collect the message.
@tparam number recipient The ID of the receiving computer. @tparam number recipient The ID of the receiving computer.
@param message The message to send. Like with @{modem.transmit}, this can @param message The message to send. Like with [`modem.transmit`], this can
contain any primitive type (numbers, booleans and strings) as well as contain any primitive type (numbers, booleans and strings) as well as
tables. Other types (like functions), as well as metatables, will not be tables. Other types (like functions), as well as metatables, will not be
transmitted. transmitted.
@tparam[opt] string protocol The "protocol" to send this message under. When @tparam[opt] string protocol The "protocol" to send this message under. When
using @{rednet.receive} one can filter to only receive messages sent under a using [`rednet.receive`] one can filter to only receive messages sent under a
particular protocol. particular protocol.
@treturn boolean If this message was successfully sent (i.e. if rednet is @treturn boolean If this message was successfully sent (i.e. if rednet is
currently @{rednet.open|open}). Note, this does not guarantee the message was currently [open][`rednet.open`]). Note, this does not guarantee the message was
actually _received_. actually _received_.
@changed 1.6 Added protocol parameter. @changed 1.6 Added protocol parameter.
@changed 1.82.0 Now returns whether the message was successfully sent. @changed 1.82.0 Now returns whether the message was successfully sent.
@ -217,13 +216,13 @@ function send(recipient, message, protocol)
return sent return sent
end end
--[[- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST} --[[- Broadcasts a string message over the predefined [`CHANNEL_BROADCAST`]
channel. The message will be received by every device listening to rednet. channel. The message will be received by every device listening to rednet.
@param message The message to send. This should not contain coroutines or @param message The message to send. This should not contain coroutines or
functions, as they will be converted to @{nil}. functions, as they will be converted to [`nil`].
@tparam[opt] string protocol The "protocol" to send this message under. When @tparam[opt] string protocol The "protocol" to send this message under. When
using @{rednet.receive} one can filter to only receive messages sent under a using [`rednet.receive`] one can filter to only receive messages sent under a
particular protocol. particular protocol.
@see rednet.receive @see rednet.receive
@changed 1.6 Added protocol parameter. @changed 1.6 Added protocol parameter.
@ -311,7 +310,7 @@ function receive(protocol_filter, timeout)
end end
--[[- Register the system as "hosting" the desired protocol under the specified --[[- Register the system as "hosting" the desired protocol under the specified
name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and name. If a rednet [lookup][`rednet.lookup`] is performed for that protocol (and
maybe name) on the same network, the registered system will automatically maybe name) on the same network, the registered system will automatically
respond via a background process, hence providing the system performing the respond via a background process, hence providing the system performing the
lookup with its ID number. lookup with its ID number.
@ -343,8 +342,8 @@ function host(protocol, hostname)
end end
end end
--- Stop @{rednet.host|hosting} a specific protocol, meaning it will no longer --- Stop [hosting][`rednet.host`] a specific protocol, meaning it will no longer
-- respond to @{rednet.lookup} requests. -- respond to [`rednet.lookup`] requests.
-- --
-- @tparam string protocol The protocol to unregister your self from. -- @tparam string protocol The protocol to unregister your self from.
-- @since 1.6 -- @since 1.6
@ -353,7 +352,7 @@ function unhost(protocol)
hostnames[protocol] = nil hostnames[protocol] = nil
end end
--[[- Search the local rednet network for systems @{rednet.host|hosting} the --[[- Search the local rednet network for systems [hosting][`rednet.host`] the
desired protocol and returns any computer IDs that respond as "registered" desired protocol and returns any computer IDs that respond as "registered"
against it. against it.
@ -365,7 +364,7 @@ match is found).
@treturn[1] number... A list of computer IDs hosting the given protocol. @treturn[1] number... A list of computer IDs hosting the given protocol.
@treturn[2] number|nil The computer ID with the provided hostname and protocol, @treturn[2] number|nil The computer ID with the provided hostname and protocol,
or @{nil} if none exists. or [`nil`] if none exists.
@since 1.6 @since 1.6
@usage Find all computers which are hosting the `"chat"` protocol. @usage Find all computers which are hosting the `"chat"` protocol.
@ -450,7 +449,7 @@ end
local started = false local started = false
--- Listen for modem messages and converts them into rednet messages, which may --- Listen for modem messages and converts them into rednet messages, which may
-- then be @{receive|received}. -- then be [received][`receive`].
-- --
-- This is automatically started in the background on computer startup, and -- This is automatically started in the background on computer startup, and
-- should not be called manually. -- should not be called manually.

View File

@ -2,13 +2,31 @@
-- --
-- SPDX-License-Identifier: LicenseRef-CCPL -- SPDX-License-Identifier: LicenseRef-CCPL
--- Read and write configuration options for CraftOS and your programs. --[[- Read and write configuration options for CraftOS and your programs.
--
-- By default, the settings API will load its configuration from the When a computer starts, it reads the current value of settings from the
-- `/.settings` file. One can then use @{settings.save} to update the file. `/.settings` file. These values then may be [read][`settings.get`] or
-- [modified][`settings.set`].
-- @module settings
-- @since 1.78 > [!WARNING]
> Calling [`settings.set`] does _not_ update the settings file by default. You
> _must_ call [`settings.save`] to persist values.
@module settings
@since 1.78
@usage Define an basic setting `123` and read its value.
settings.define("my.setting", {
description = "An example setting",
default = 123,
type = number,
})
print("my.setting = " .. settings.get("my.setting")) -- 123
You can then use the `set` program to change its value (e.g. `set my.setting 456`),
and then re-run the `example` program to check it has changed.
]]
local expect = dofile("rom/modules/main/cc/expect.lua") local expect = dofile("rom/modules/main/cc/expect.lua")
local type, expect, field = type, expect.expect, expect.field local type, expect, field = type, expect.expect, expect.field
@ -40,9 +58,9 @@ for _, v in ipairs(valid_types) do valid_types[v] = true end
-- Options for this setting. This table accepts the following fields: -- Options for this setting. This table accepts the following fields:
-- --
-- - `description`: A description which may be printed when running the `set` program. -- - `description`: A description which may be printed when running the `set` program.
-- - `default`: A default value, which is returned by @{settings.get} if the -- - `default`: A default value, which is returned by [`settings.get`] if the
-- setting has not been changed. -- setting has not been changed.
-- - `type`: Require values to be of this type. @{set|Setting} the value to another type -- - `type`: Require values to be of this type. [Setting][`set`] the value to another type
-- will error. -- will error.
-- @since 1.87.0 -- @since 1.87.0
function define(name, options) function define(name, options)
@ -66,9 +84,9 @@ function define(name, options)
details[name] = options details[name] = options
end end
--- Remove a @{define|definition} of a setting. --- Remove a [definition][`define`] of a setting.
-- --
-- If a setting has been changed, this does not remove its value. Use @{settings.unset} -- If a setting has been changed, this does not remove its value. Use [`settings.unset`]
-- for that. -- for that.
-- --
-- @tparam string name The name of this option -- @tparam string name The name of this option
@ -92,13 +110,18 @@ local function set_value(name, new)
end end
end end
--- Set the value of a setting. --[[- Set the value of a setting.
--
-- @tparam string name The name of the setting to set > [!WARNING]
-- @param value The setting's value. This cannot be `nil`, and must be > Calling [`settings.set`] does _not_ update the settings file by default. You
-- serialisable by @{textutils.serialize}. > _must_ call [`settings.save`] to persist values.
-- @throws If this value cannot be serialised
-- @see settings.unset @tparam string name The name of the setting to set
@param value The setting's value. This cannot be `nil`, and must be
serialisable by [`textutils.serialize`].
@throws If this value cannot be serialised
@see settings.unset
]]
function set(name, value) function set(name, value)
expect(1, name, "string") expect(1, name, "string")
expect(2, value, "number", "string", "boolean", "table") expect(2, value, "number", "string", "boolean", "table")
@ -134,7 +157,7 @@ end
-- --
-- @tparam string name The name of the setting to get. -- @tparam string name The name of the setting to get.
-- @treturn { description? = string, default? = any, type? = string, value? = any } -- @treturn { description? = string, default? = any, type? = string, value? = any }
-- Information about this setting. This includes all information from @{settings.define}, -- Information about this setting. This includes all information from [`settings.define`],
-- as well as this setting's value. -- as well as this setting's value.
-- @since 1.87.0 -- @since 1.87.0
function getDetails(name) function getDetails(name)
@ -148,8 +171,8 @@ end
--- Remove the value of a setting, setting it to the default. --- Remove the value of a setting, setting it to the default.
-- --
-- @{settings.get} will return the default value until the setting's value is -- [`settings.get`] will return the default value until the setting's value is
-- @{settings.set|set}, or the computer is rebooted. -- [set][`settings.set`], or the computer is rebooted.
-- --
-- @tparam string name The name of the setting to unset. -- @tparam string name The name of the setting to unset.
-- @see settings.set -- @see settings.set
@ -159,7 +182,7 @@ function unset(name)
set_value(name, nil) set_value(name, nil)
end end
--- Resets the value of all settings. Equivalent to calling @{settings.unset} --- Resets the value of all settings. Equivalent to calling [`settings.unset`]
--- on every setting. --- on every setting.
-- --
-- @see settings.unset -- @see settings.unset

View File

@ -17,9 +17,9 @@ end
local term = _ENV local term = _ENV
--- Redirects terminal output to a monitor, a @{window}, or any other custom --- Redirects terminal output to a monitor, a [`window`], or any other custom
-- terminal object. Once the redirect is performed, any calls to a "term" -- terminal object. Once the redirect is performed, any calls to a "term"
-- function - or to a function that makes use of a term function, as @{print} - -- function - or to a function that makes use of a term function, as [`print`] -
-- will instead operate with the new terminal object. -- will instead operate with the new terminal object.
-- --
-- A "terminal object" is simply a table that contains functions with the same -- A "terminal object" is simply a table that contains functions with the same
@ -29,9 +29,9 @@ local term = _ENV
-- The redirect can be undone by pointing back to the previous terminal object -- The redirect can be undone by pointing back to the previous terminal object
-- (which this function returns whenever you switch). -- (which this function returns whenever you switch).
-- --
-- @tparam Redirect target The terminal redirect the @{term} API will draw to. -- @tparam Redirect target The terminal redirect the [`term`] API will draw to.
-- @treturn Redirect The previous redirect object, as returned by -- @treturn Redirect The previous redirect object, as returned by
-- @{term.current}. -- [`term.current`].
-- @since 1.31 -- @since 1.31
-- @usage -- @usage
-- Redirect to a monitor on the right of the computer. -- Redirect to a monitor on the right of the computer.
@ -60,7 +60,7 @@ end
-- @treturn Redirect The current terminal redirect -- @treturn Redirect The current terminal redirect
-- @since 1.6 -- @since 1.6
-- @usage -- @usage
-- Create a new @{window} which draws to the current redirect target. -- Create a new [`window`] which draws to the current redirect target.
-- --
-- window.create(term.current(), 1, 1, 10, 10) -- window.create(term.current(), 1, 1, 10, 10)
term.current = function() term.current = function()
@ -70,7 +70,7 @@ end
--- Get the native terminal object of the current computer. --- Get the native terminal object of the current computer.
-- --
-- It is recommended you do not use this function unless you absolutely have -- It is recommended you do not use this function unless you absolutely have
-- to. In a multitasked environment, @{term.native} will _not_ be the current -- to. In a multitasked environment, [`term.native`] will _not_ be the current
-- terminal object, and so drawing may interfere with other programs. -- terminal object, and so drawing may interfere with other programs.
-- --
-- @treturn Redirect The native terminal redirect. -- @treturn Redirect The native terminal redirect.

View File

@ -14,7 +14,7 @@ local wrap = dofile("rom/modules/main/cc/strings.lua").wrap
--- Slowly writes string text at current cursor position, --- Slowly writes string text at current cursor position,
-- character-by-character. -- character-by-character.
-- --
-- Like @{_G.write}, this does not insert a newline at the end. -- Like [`_G.write`], this does not insert a newline at the end.
-- --
-- @tparam string text The the text to write to the screen -- @tparam string text The the text to write to the screen
-- @tparam[opt] number rate The number of characters to write each second, -- @tparam[opt] number rate The number of characters to write each second,
@ -42,7 +42,7 @@ end
--- Slowly prints string text at current cursor position, --- Slowly prints string text at current cursor position,
-- character-by-character. -- character-by-character.
-- --
-- Like @{print}, this inserts a newline after printing. -- Like [`print`], this inserts a newline after printing.
-- --
-- @tparam string sText The the text to write to the screen -- @tparam string sText The the text to write to the screen
-- @tparam[opt] number nRate The number of characters to write each second, -- @tparam[opt] number nRate The number of characters to write each second,
@ -56,7 +56,7 @@ end
--- Takes input time and formats it in a more readable format such as `6:30 PM`. --- Takes input time and formats it in a more readable format such as `6:30 PM`.
-- --
-- @tparam number nTime The time to format, as provided by @{os.time}. -- @tparam number nTime The time to format, as provided by [`os.time`].
-- @tparam[opt] boolean bTwentyFourHour Whether to format this as a 24-hour -- @tparam[opt] boolean bTwentyFourHour Whether to format this as a 24-hour
-- clock (`18:30`) rather than a 12-hour one (`6:30 AM`) -- clock (`18:30`) rather than a 12-hour one (`6:30 AM`)
-- @treturn string The formatted time -- @treturn string The formatted time
@ -114,7 +114,7 @@ end
--[[- Prints a given string to the display. --[[- Prints a given string to the display.
If the action can be completed without scrolling, it acts much the same as If the action can be completed without scrolling, it acts much the same as
@{print}; otherwise, it will throw up a "Press any key to continue" prompt at [`print`]; otherwise, it will throw up a "Press any key to continue" prompt at
the bottom of the display. Each press will cause it to scroll down and write a the bottom of the display. Each press will cause it to scroll down and write a
single line more before prompting again, if need be. single line more before prompting again, if need be.
@ -253,7 +253,7 @@ end
--[[- Prints tables in a structured form, stopping and prompting for input should --[[- Prints tables in a structured form, stopping and prompting for input should
the result not fit on the terminal. the result not fit on the terminal.
This functions identically to @{textutils.tabulate}, but will prompt for user This functions identically to [`textutils.tabulate`], but will prompt for user
input should the whole output not fit on the display. input should the whole output not fit on the display.
@tparam {string...}|number ... The rows and text colors to display. @tparam {string...}|number ... The rows and text colors to display.
@ -424,12 +424,31 @@ do
if map[c] == nil then map[c] = hexify(c) end if map[c] == nil then map[c] = hexify(c) end
end end
serializeJSONString = function(s) serializeJSONString = function(s, options)
if options and options.unicode_strings and s:find("[\x80-\xff]") then
local retval = '"'
for _, code in utf8.codes(s) do
if code > 0xFFFF then
-- Encode the codepoint as a UTF-16 surrogate pair
code = code - 0x10000
local high, low = bit32.extract(code, 10, 10) + 0xD800, bit32.extract(code, 0, 10) + 0xDC00
retval = retval .. ("\\u%04X\\u%04X"):format(high, low)
elseif code <= 0x5C and map[string.char(code)] then -- 0x5C = `\`, don't run `string.char` if we don't need to
retval = retval .. map[string.char(code)]
elseif code < 0x20 or code >= 0x7F then
retval = retval .. ("\\u%04X"):format(code)
else
retval = retval .. string.char(code)
end
end
return retval .. '"'
else
return ('"%s"'):format(s:gsub("[\0-\x1f\"\\]", map):gsub("[\x7f-\xff]", hexify)) return ('"%s"'):format(s:gsub("[\0-\x1f\"\\]", map):gsub("[\x7f-\xff]", hexify))
end end
end
end end
local function serializeJSONImpl(t, tTracking, bNBTStyle) local function serializeJSONImpl(t, tTracking, options)
local sType = type(t) local sType = type(t)
if t == empty_json_array then return "[]" if t == empty_json_array then return "[]"
elseif t == json_null then return "null" elseif t == json_null then return "null"
@ -450,13 +469,14 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
local nObjectSize = 0 local nObjectSize = 0
local nArraySize = 0 local nArraySize = 0
local largestArrayIndex = 0 local largestArrayIndex = 0
local bNBTStyle = options and options.nbt_style
for k, v in pairs(t) do for k, v in pairs(t) do
if type(k) == "string" then if type(k) == "string" then
local sEntry local sEntry
if bNBTStyle then if bNBTStyle then
sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle) sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, options)
else else
sEntry = serializeJSONString(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle) sEntry = serializeJSONString(k, options) .. ":" .. serializeJSONImpl(v, tTracking, options)
end end
if nObjectSize == 0 then if nObjectSize == 0 then
sObjectResult = sObjectResult .. sEntry sObjectResult = sObjectResult .. sEntry
@ -473,7 +493,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
if t[k] == nil then --if the array is nil at index k the value is "null" as to keep the unused indexes in between used ones. if t[k] == nil then --if the array is nil at index k the value is "null" as to keep the unused indexes in between used ones.
sEntry = "null" sEntry = "null"
else -- if the array index does not point to a nil we serialise it's content. else -- if the array index does not point to a nil we serialise it's content.
sEntry = serializeJSONImpl(t[k], tTracking, bNBTStyle) sEntry = serializeJSONImpl(t[k], tTracking, options)
end end
if nArraySize == 0 then if nArraySize == 0 then
sArrayResult = sArrayResult .. sEntry sArrayResult = sArrayResult .. sEntry
@ -492,7 +512,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
end end
elseif sType == "string" then elseif sType == "string" then
return serializeJSONString(t) return serializeJSONString(t, options)
elseif sType == "number" or sType == "boolean" then elseif sType == "number" or sType == "boolean" then
return tostring(t) return tostring(t)
@ -682,13 +702,13 @@ do
--[[- Converts a serialised JSON string back into a reassembled Lua object. --[[- Converts a serialised JSON string back into a reassembled Lua object.
This may be used with @{textutils.serializeJSON}, or when communicating This may be used with [`textutils.serializeJSON`], or when communicating
with command blocks or web APIs. with command blocks or web APIs.
If a `null` value is encountered, it is converted into `nil`. It can be converted If a `null` value is encountered, it is converted into `nil`. It can be converted
into @{textutils.json_null} with the `parse_null` option. into [`textutils.json_null`] with the `parse_null` option.
If an empty array is encountered, it is converted into @{textutils.empty_json_array}. If an empty array is encountered, it is converted into [`textutils.empty_json_array`].
It can be converted into a new empty table with the `parse_empty_array` option. It can be converted into a new empty table with the `parse_empty_array` option.
@tparam string s The serialised string to deserialise. @tparam string s The serialised string to deserialise.
@ -697,12 +717,12 @@ do
- `nbt_style`: When true, this will accept [stringified NBT][nbt] strings, - `nbt_style`: When true, this will accept [stringified NBT][nbt] strings,
as produced by many commands. as produced by many commands.
- `parse_null`: When true, `null` will be parsed as @{json_null}, rather than - `parse_null`: When true, `null` will be parsed as [`json_null`], rather than
`nil`. `nil`.
- `parse_empty_array`: When false, empty arrays will be parsed as a new table. - `parse_empty_array`: When false, empty arrays will be parsed as a new table.
By default (or when this value is true), they are parsed as @{empty_json_array}. By default (or when this value is true), they are parsed as [`empty_json_array`].
[nbt]: https://minecraft.gamepedia.com/NBT_format [nbt]: https://minecraft.wiki/w/NBT_format
@return[1] The deserialised object @return[1] The deserialised object
@treturn[2] nil If the object could not be deserialised. @treturn[2] nil If the object could not be deserialised.
@treturn string A message describing why the JSON string is invalid. @treturn string A message describing why the JSON string is invalid.
@ -714,7 +734,7 @@ do
textutils.unserialiseJSON('{"name": "Steve", "age": null}') textutils.unserialiseJSON('{"name": "Steve", "age": null}')
@usage Unserialise a basic JSON object, returning null values as @{json_null}. @usage Unserialise a basic JSON object, returning null values as [`json_null`].
textutils.unserialiseJSON('{"name": "Steve", "age": null}', { parse_null = true }) textutils.unserialiseJSON('{"name": "Steve", "age": null}', { parse_null = true })
]] ]]
@ -793,7 +813,7 @@ serialise = serialize -- GB version
--- Converts a serialised string back into a reassembled Lua object. --- Converts a serialised string back into a reassembled Lua object.
-- --
-- This is mainly used together with @{textutils.serialise}. -- This is mainly used together with [`textutils.serialise`].
-- --
-- @tparam string s The serialised string to deserialise. -- @tparam string s The serialised string to deserialise.
-- @return[1] The deserialised object -- @return[1] The deserialised object
@ -813,32 +833,57 @@ end
unserialise = unserialize -- GB version unserialise = unserialize -- GB version
--- Returns a JSON representation of the given data. --[[- Returns a JSON representation of the given data.
--
-- This function attempts to guess whether a table is a JSON array or This function attempts to guess whether a table is a JSON array or
-- object. However, empty tables are assumed to be empty objects - use object. However, empty tables are assumed to be empty objects - use
-- @{textutils.empty_json_array} to mark an empty array. [`textutils.empty_json_array`] to mark an empty array.
--
-- This is largely intended for interacting with various functions from the This is largely intended for interacting with various functions from the
-- @{commands} API, though may also be used in making @{http} requests. [`commands`] API, though may also be used in making [`http`] requests.
--
-- @param t The value to serialise. Like @{textutils.serialise}, this should not @param[1] t The value to serialise. Like [`textutils.serialise`], this should not
-- contain recursive tables or functions. contain recursive tables or functions.
-- @tparam[opt] boolean bNBTStyle Whether to produce NBT-style JSON (non-quoted keys) @tparam[1,opt] { nbt_style? = boolean, unicode_strings? = boolean } options Options for serialisation.
-- instead of standard JSON. - `nbt_style`: Whether to produce NBT-style JSON (non-quoted keys) instead of standard JSON.
-- @treturn string The JSON representation of the input. - `unicode_strings`: Whether to treat strings as containing UTF-8 characters instead of
-- @throws If the object contains a value which cannot be using the default 8-bit character set.
-- serialised. This includes functions and tables which appear multiple
-- times. @param[2] t The value to serialise. Like [`textutils.serialise`], this should not
-- @usage textutils.serialiseJSON({ values = { 1, "2", true } }) contain recursive tables or functions.
-- @since 1.7 @tparam[2] boolean bNBTStyle Whether to produce NBT-style JSON (non-quoted keys)
-- @see textutils.json_null Use to serialise a JSON `null` value. instead of standard JSON.
-- @see textutils.empty_json_array Use to serialise a JSON empty array.
function serializeJSON(t, bNBTStyle) @treturn string The JSON representation of the input.
@throws If the object contains a value which cannot be serialised. This includes
functions and tables which appear multiple times.
@usage Serialise a simple object
textutils.serialiseJSON({ values = { 1, "2", true } })
@usage Serialise an object to a NBT-style string
textutils.serialiseJSON({ values = { 1, "2", true } }, { nbt_style = true })
@since 1.7
@changed 1.106.0 Added `options` overload and `unicode_strings` option.
@see textutils.json_null Use to serialise a JSON `null` value.
@see textutils.empty_json_array Use to serialise a JSON empty array.
]]
function serializeJSON(t, options)
expect(1, t, "table", "string", "number", "boolean") expect(1, t, "table", "string", "number", "boolean")
expect(2, bNBTStyle, "boolean", "nil") expect(2, options, "table", "boolean", "nil")
if type(options) == "boolean" then
options = { nbt_style = options }
elseif type(options) == "table" then
field(options, "nbt_style", "boolean", "nil")
field(options, "unicode_strings", "boolean", "nil")
end
local tTracking = {} local tTracking = {}
return serializeJSONImpl(t, tTracking, bNBTStyle or false) return serializeJSONImpl(t, tTracking, options)
end end
serialiseJSON = serializeJSON -- GB version serialiseJSON = serializeJSON -- GB version
@ -884,7 +929,7 @@ local tEmpty = {}
-- variable name or table index. -- variable name or table index.
-- --
-- @tparam[opt] table tSearchTable The table to find variables in, defaulting to -- @tparam[opt] table tSearchTable The table to find variables in, defaulting to
-- the global environment (@{_G}). The function also searches the "parent" -- the global environment ([`_G`]). The function also searches the "parent"
-- environment via the `__index` metatable field. -- environment via the `__index` metatable field.
-- --
-- @treturn { string... } The (possibly empty) list of completions. -- @treturn { string... } The (possibly empty) list of completions.

View File

@ -4,7 +4,7 @@
--- A basic 3D vector type and some common vector operations. This may be useful --- A basic 3D vector type and some common vector operations. This may be useful
-- when working with coordinates in Minecraft's world (such as those from the -- when working with coordinates in Minecraft's world (such as those from the
-- @{gps} API). -- [`gps`] API).
-- --
-- An introduction to vectors can be found on [Wikipedia][wiki]. -- An introduction to vectors can be found on [Wikipedia][wiki].
-- --
@ -180,7 +180,7 @@ local vmetatable = {
__eq = vector.equals, __eq = vector.equals,
} }
--- Construct a new @{Vector} with the given coordinates. --- Construct a new [`Vector`] with the given coordinates.
-- --
-- @tparam number x The X coordinate or direction of the vector. -- @tparam number x The X coordinate or direction of the vector.
-- @tparam number y The Y coordinate or direction of the vector. -- @tparam number y The Y coordinate or direction of the vector.

View File

@ -2,10 +2,10 @@
-- --
-- SPDX-License-Identifier: LicenseRef-CCPL -- SPDX-License-Identifier: LicenseRef-CCPL
--[[- A @{term.Redirect|terminal redirect} occupying a smaller area of an --[[- A [terminal redirect][`term.Redirect`] occupying a smaller area of an
existing terminal. This allows for easy definition of spaces within the display existing terminal. This allows for easy definition of spaces within the display
that can be written/drawn to, then later redrawn/repositioned/etc as need that can be written/drawn to, then later redrawn/repositioned/etc as need
be. The API itself contains only one function, @{window.create}, which returns be. The API itself contains only one function, [`window.create`], which returns
the windows themselves. the windows themselves.
Windows are considered terminal objects - as such, they have access to nearly Windows are considered terminal objects - as such, they have access to nearly
@ -60,11 +60,11 @@ local string_sub = string.sub
--[[- Returns a terminal object that is a space within the specified parent --[[- Returns a terminal object that is a space within the specified parent
terminal object. This can then be used (or even redirected to) in the same terminal object. This can then be used (or even redirected to) in the same
manner as eg a wrapped monitor. Refer to @{term|the term API} for a list of manner as eg a wrapped monitor. Refer to [the term API][`term`] for a list of
functions available to it. functions available to it.
@{term} itself may not be passed as the parent, though @{term.native} is [`term`] itself may not be passed as the parent, though [`term.native`] is
acceptable. Generally, @{term.current} or a wrapped monitor will be most acceptable. Generally, [`term.current`] or a wrapped monitor will be most
suitable, though windows may even have other windows assigned as their suitable, though windows may even have other windows assigned as their
parents. parents.
@ -131,11 +131,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local sEmptyTextColor = tEmptyColorLines[nTextColor] local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, nHeight do for y = 1, nHeight do
tLines[y] = { tLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
end end
for i = 0, 15 do for i = 0, 15 do
@ -165,7 +161,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local function redrawLine(n) local function redrawLine(n)
local tLine = tLines[n] local tLine = tLines[n]
parent.setCursorPos(nX, nY + n - 1) parent.setCursorPos(nX, nY + n - 1)
parent.blit(tLine.text, tLine.textColor, tLine.backgroundColor) parent.blit(tLine[1], tLine[2], tLine[3])
end end
local function redraw() local function redraw()
@ -188,9 +184,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
-- Modify line -- Modify line
local tLine = tLines[nCursorY] local tLine = tLines[nCursorY]
if nStart == 1 and nEnd == nWidth then if nStart == 1 and nEnd == nWidth then
tLine.text = sText tLine[1] = sText
tLine.textColor = sTextColor tLine[2] = sTextColor
tLine.backgroundColor = sBackgroundColor tLine[3] = sBackgroundColor
else else
local sClippedText, sClippedTextColor, sClippedBackgroundColor local sClippedText, sClippedTextColor, sClippedBackgroundColor
if nStart < 1 then if nStart < 1 then
@ -210,9 +206,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
sClippedBackgroundColor = sBackgroundColor sClippedBackgroundColor = sBackgroundColor
end end
local sOldText = tLine.text local sOldText = tLine[1]
local sOldTextColor = tLine.textColor local sOldTextColor = tLine[2]
local sOldBackgroundColor = tLine.backgroundColor local sOldBackgroundColor = tLine[3]
local sNewText, sNewTextColor, sNewBackgroundColor local sNewText, sNewTextColor, sNewBackgroundColor
if nStart > 1 then if nStart > 1 then
local nOldEnd = nStart - 1 local nOldEnd = nStart - 1
@ -231,9 +227,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth) sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth)
end end
tLine.text = sNewText tLine[1] = sNewText
tLine.textColor = sNewTextColor tLine[2] = sNewTextColor
tLine.backgroundColor = sNewBackgroundColor tLine[3] = sNewBackgroundColor
end end
-- Redraw line -- Redraw line
@ -251,7 +247,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
end end
end end
--- The window object. Refer to the @{window|module's documentation} for --- The window object. Refer to the [module's documentation][`window`] for
-- a full description. -- a full description.
-- --
-- @type Window -- @type Window
@ -280,11 +276,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local sEmptyTextColor = tEmptyColorLines[nTextColor] local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, nHeight do for y = 1, nHeight do
tLines[y] = { local line = tLines[y]
text = sEmptyText, line[1] = sEmptyText
textColor = sEmptyTextColor, line[2] = sEmptyTextColor
backgroundColor = sEmptyBackgroundColor, line[3] = sEmptyBackgroundColor
}
end end
if bVisible then if bVisible then
redraw() redraw()
@ -295,14 +290,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
function window.clearLine() function window.clearLine()
if nCursorY >= 1 and nCursorY <= nHeight then if nCursorY >= 1 and nCursorY <= nHeight then
local sEmptyText = sEmptySpaceLine local line = tLines[nCursorY]
local sEmptyTextColor = tEmptyColorLines[nTextColor] line[1] = sEmptySpaceLine
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] line[2] = tEmptyColorLines[nTextColor]
tLines[nCursorY] = { line[3] = tEmptyColorLines[nBackgroundColor]
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
if bVisible then if bVisible then
redrawLine(nCursorY) redrawLine(nCursorY)
updateCursorColor() updateCursorColor()
@ -431,11 +422,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
if y >= 1 and y <= nHeight then if y >= 1 and y <= nHeight then
tNewLines[newY] = tLines[y] tNewLines[newY] = tLines[y]
else else
tNewLines[newY] = { tNewLines[newY] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
end end
end end
tLines = tNewLines tLines = tNewLines
@ -467,8 +454,8 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
-- --
-- @tparam number y The y position of the line to get. -- @tparam number y The y position of the line to get.
-- @treturn string The textual content of this line. -- @treturn string The textual content of this line.
-- @treturn string The text colours of this line, suitable for use with @{term.blit}. -- @treturn string The text colours of this line, suitable for use with [`term.blit`].
-- @treturn string The background colours of this line, suitable for use with @{term.blit}. -- @treturn string The background colours of this line, suitable for use with [`term.blit`].
-- @throws If `y` is not between 1 and this window's height. -- @throws If `y` is not between 1 and this window's height.
-- @since 1.84.0 -- @since 1.84.0
function window.getLine(y) function window.getLine(y)
@ -478,7 +465,8 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
error("Line is out of range.", 2) error("Line is out of range.", 2)
end end
return tLines[y].text, tLines[y].textColor, tLines[y].backgroundColor local line = tLines[y]
return line[1], line[2], line[3]
end end
-- Other functions -- Other functions
@ -574,26 +562,22 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, new_height do for y = 1, new_height do
if y > nHeight then if y > nHeight then
tNewLines[y] = { tNewLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
else else
local tOldLine = tLines[y] local tOldLine = tLines[y]
if new_width == nWidth then if new_width == nWidth then
tNewLines[y] = tOldLine tNewLines[y] = tOldLine
elseif new_width < nWidth then elseif new_width < nWidth then
tNewLines[y] = { tNewLines[y] = {
text = string_sub(tOldLine.text, 1, new_width), string_sub(tOldLine[1], 1, new_width),
textColor = string_sub(tOldLine.textColor, 1, new_width), string_sub(tOldLine[2], 1, new_width),
backgroundColor = string_sub(tOldLine.backgroundColor, 1, new_width), string_sub(tOldLine[3], 1, new_width),
} }
else else
tNewLines[y] = { tNewLines[y] = {
text = tOldLine.text .. string_sub(sEmptyText, nWidth + 1, new_width), tOldLine[1] .. string_sub(sEmptyText, nWidth + 1, new_width),
textColor = tOldLine.textColor .. string_sub(sEmptyTextColor, nWidth + 1, new_width), tOldLine[2] .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
backgroundColor = tOldLine.backgroundColor .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width), tOldLine[3] .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
} }
end end
end end

View File

@ -1,3 +1,62 @@
# New features in CC: Tweaked 1.108.1
Several bug fixes:
* Prevent no-opped players breaking or placing command computers.
* Allow using `@LuaFunction`-annotated methods on classes defined in child classloaders.
# New features in CC: Tweaked 1.108.0
* Remove compression from terminal/monitor packets. Vanilla applies its own compression, so this ends up being less helpful than expected.
* `/computercraft` command now supports permission mods.
* Split some GUI textures into sprite sheets.
* Support the `%g` character class in string pattern matching.
Several bug fixes:
* Fix crash when playing some modded records via a disk drive.
* Fix race condition when computers attach or detach from a monitor.
* Fix the "max websocket message" config option not being read.
* `tostring` now correctly obeys `__name`.
* Fix several inconsistencies with pattern matching character classes.
# New features in CC: Tweaked 1.107.0
* Add `disabled_generic_methods` config option to disable generic methods.
* Add basic integration with EMI.
* Enchanted turtle tools now render with a glint.
* Update several translations (PatriikPlays, 1Turtle, Ale32bit).
Several bug fixes:
* Fix client config file being generated on a dedicated server.
* Fix numbers ending in "f" or "d" being treated as avalid.
* Fix `string.pack`'s "z" specifier causing out-of-bounds errors.
* Fix several issues with `turtle.dig`'s custom actions (tilling, making paths).
# New features in CC: Tweaked 1.106.1
Several bug fixes:
* Block the CGNAT range (100.64.0.0/10) by default.
* Fix conflicts with other mods replacing reach distance.
# New features in CC: Tweaked 1.106.0
* Numerous documentation improvements (MCJack123, znepb, penguinencounter).
* Port `fs.find` to Lua. This also allows using `?` as a wildcard.
* Computers cursors now glow in the dark.
* Allow changing turtle upgrades from the GUI.
* Add option to serialize Unicode strings to JSON (MCJack123).
* Small optimisations to the `window` API.
* Turtle upgrades can now preserve NBT from upgrade item stack and when broken.
* Add support for tool enchantments and durability via datapacks. This is disabled for the built-in tools.
Several bug fixes:
* Fix turtles rendering incorrectly when upside down.
* Fix misplaced calls to IArguments.escapes.
* Lua REPL no longer accepts `)(` as a valid expression.
* Fix several inconsistencies with `require`/`package.path` in the Lua REPL (Wojbie).
* Fix turtle being able to place water buckets outside its reach distance.
* Fix private several IP address ranges not being blocked by the `$private` rule.
* Improve permission checks in the `/computercraft` command.
# New features in CC: Tweaked 1.105.0 # New features in CC: Tweaked 1.105.0
* Optimise JSON string parsing. * Optimise JSON string parsing.

View File

@ -12,4 +12,4 @@ commands.give( "dan200", "minecraft:diamond", 64 )
This works with any command. Use "commands.async" instead of "commands" to execute asynchronously. This works with any command. Use "commands.async" instead of "commands" to execute asynchronously.
The commands API is only available on Command Computers. The commands API is only available on Command Computers.
Visit http://minecraft.gamepedia.com/Commands for documentation on all commands. Visit https://minecraft.wiki/w/Commands for documentation on all commands.

View File

@ -6,4 +6,4 @@ if sEvent == "key" and nKey == keys.enter then
-- Do something -- Do something
end end
See http://www.minecraftwiki.net/wiki/Key_codes, or the source code, for a complete reference. See https://www.minecraft.wiki/w/Key_codes, or the source code, for a complete reference.

View File

@ -1,25 +1,7 @@
New features in CC: Tweaked 1.105.0 New features in CC: Tweaked 1.108.1
* Optimise JSON string parsing.
* Add `colors.fromBlit` (Erb3).
* Upload file size limit is now configurable (khankul).
* Wired cables no longer have a distance limit.
* Java methods now coerce values to strings consistently with Lua.
* Add custom timeout support to the HTTP API.
* Support custom proxies for HTTP requests (Lemmmy).
* The `speaker` program now errors when playing HTML files.
* `edit` now shows an error message when editing read-only files.
* Update Ukranian translation (SirEdvin).
Several bug fixes: Several bug fixes:
* Allow GPS hosts to only be 1 block apart. * Prevent no-opped players breaking or placing command computers.
* Fix "Turn On"/"Turn Off" buttons being inverted in the computer GUI (Erb3). * Allow using `@LuaFunction`-annotated methods on classes defined in child classloaders.
* Fix arrow keys not working in the printout UI.
* Several documentation fixes (zyxkad, Lupus590, Commandcracker).
* Fix monitor renderer debug text always being visible on Forge.
* Fix crash when another mod changes the LoggerContext.
* Fix the `monitor_renderer` option not being present in Fabric config files.
* Pasting on MacOS/OSX now uses Cmd+V rather than Ctrl+V.
* Fix turtles placing blocks upside down when at y<0.
Type "help changelog" to see the full version history. Type "help changelog" to see the full version history.

View File

@ -9,11 +9,11 @@ DFPWM (Dynamic Filter Pulse Width Modulation) is an audio codec designed by Grea
format compared to raw PCM data, only using 1 bit per sample, but is simple enough to simple enough to encode and decode format compared to raw PCM data, only using 1 bit per sample, but is simple enough to simple enough to encode and decode
in real time. in real time.
Typically DFPWM audio is read from @{fs.BinaryReadHandle|the filesystem} or a @{http.Response|a web request} as a Typically DFPWM audio is read from [the filesystem][`fs.BinaryReadHandle`] or a [a web request][`http.Response`] as a
string, and converted a format suitable for @{speaker.playAudio}. string, and converted a format suitable for [`speaker.playAudio`].
## Encoding and decoding files ## Encoding and decoding files
This modules exposes two key functions, @{make_decoder} and @{make_encoder}, which construct a new decoder or encoder. This modules exposes two key functions, [`make_decoder`] and [`make_encoder`], which construct a new decoder or encoder.
The returned encoder/decoder is itself a function, which converts between the two kinds of data. The returned encoder/decoder is itself a function, which converts between the two kinds of data.
These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for
@ -95,10 +95,9 @@ end
The returned encoder is itself a function. This function accepts a table of amplitude data between -128 and 127 and The returned encoder is itself a function. This function accepts a table of amplitude data between -128 and 127 and
returns the encoded DFPWM data. returns the encoded DFPWM data.
:::caution Reusing encoders > [Reusing encoders][!WARNING]
Encoders have lots of internal state which tracks the state of the current stream. If you reuse an encoder for multiple > Encoders have lots of internal state which tracks the state of the current stream. If you reuse an encoder for multiple
streams, or use different encoders for the same stream, the resulting audio may not sound correct. > streams, or use different encoders for the same stream, the resulting audio may not sound correct.
:::
@treturn function(pcm: { number... }):string The encoder function @treturn function(pcm: { number... }):string The encoder function
@see encode A helper function for encoding an entire file of audio at once. @see encode A helper function for encoding an entire file of audio at once.
@ -138,10 +137,9 @@ end
The returned decoder is itself a function. This function accepts a string and returns a table of amplitudes, each value The returned decoder is itself a function. This function accepts a string and returns a table of amplitudes, each value
between -128 and 127. between -128 and 127.
:::caution Reusing decoders > [Reusing decoders][!WARNING]
Decoders have lots of internal state which tracks the state of the current stream. If you reuse an decoder for multiple > Decoders have lots of internal state which tracks the state of the current stream. If you reuse an decoder for
streams, or use different decoders for the same stream, the resulting audio may not sound correct. > multiple streams, or use different decoders for the same stream, the resulting audio may not sound correct.
:::
@treturn function(dfpwm: string):{ number... } The encoder function @treturn function(dfpwm: string):{ number... } The encoder function
@see decode A helper function for decoding an entire file of audio at once. @see decode A helper function for decoding an entire file of audio at once.
@ -167,7 +165,7 @@ local function make_decoder()
local low_pass_charge = 0 local low_pass_charge = 0
local previous_charge, previous_bit = 0, false local previous_charge, previous_bit = 0, false
return function (input, output) return function (input)
expect(1, input, "string") expect(1, input, "string")
local output, output_n = {}, 0 local output, output_n = {}, 0
@ -200,7 +198,7 @@ end
--[[- A convenience function for decoding a complete file of audio at once. --[[- A convenience function for decoding a complete file of audio at once.
This should only be used for short files. For larger files, one should read the file in chunks and process it using This should only be used for short files. For larger files, one should read the file in chunks and process it using
@{make_decoder}. [`make_decoder`].
@tparam string input The DFPWM data to convert. @tparam string input The DFPWM data to convert.
@treturn { number... } The produced amplitude data. @treturn { number... } The produced amplitude data.
@ -214,7 +212,7 @@ end
--[[- A convenience function for encoding a complete file of audio at once. --[[- A convenience function for encoding a complete file of audio at once.
This should only be used for complete pieces of audio. If you are writing writing multiple chunks to the same place, This should only be used for complete pieces of audio. If you are writing writing multiple chunks to the same place,
you should use an encoder returned by @{make_encoder} instead. you should use an encoder returned by [`make_encoder`] instead.
@tparam { number... } input The table of amplitude data. @tparam { number... } input The table of amplitude data.
@treturn string The encoded DFPWM data. @treturn string The encoded DFPWM data.

View File

@ -3,11 +3,11 @@
-- SPDX-License-Identifier: LicenseRef-CCPL -- SPDX-License-Identifier: LicenseRef-CCPL
--- A collection of helper methods for working with input completion, such --- A collection of helper methods for working with input completion, such
-- as that require by @{_G.read}. -- as that require by [`_G.read`].
-- --
-- @module cc.completion -- @module cc.completion
-- @see cc.shell.completion For additional helpers to use with -- @see cc.shell.completion For additional helpers to use with
-- @{shell.setCompletionFunction}. -- [`shell.setCompletionFunction`].
-- @since 1.85.0 -- @since 1.85.0
local expect = require "cc.expect".expect local expect = require "cc.expect".expect
@ -34,7 +34,7 @@ end
-- @tparam { string... } choices The list of choices to complete from. -- @tparam { string... } choices The list of choices to complete from.
-- @tparam[opt] boolean add_space Whether to add a space after the completed item. -- @tparam[opt] boolean add_space Whether to add a space after the completed item.
-- @treturn { string... } A list of suffixes of matching strings. -- @treturn { string... } A list of suffixes of matching strings.
-- @usage Call @{_G.read}, completing the names of various animals. -- @usage Call [`_G.read`], completing the names of various animals.
-- --
-- local completion = require "cc.completion" -- local completion = require "cc.completion"
-- local animals = { "dog", "cat", "lion", "unicorn" } -- local animals = { "dog", "cat", "lion", "unicorn" }
@ -76,7 +76,7 @@ local function side(text, add_space)
return choice_impl(text, sides, add_space) return choice_impl(text, sides, add_space)
end end
--- Complete a @{settings|setting}. --- Complete a [setting][`settings`].
-- --
-- @tparam string text The input string to complete. -- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed settings. -- @tparam[opt] boolean add_space Whether to add a space after the completed settings.
@ -92,7 +92,7 @@ end
local command_list local command_list
--- Complete the name of a Minecraft @{commands|command}. --- Complete the name of a Minecraft [command][`commands`].
-- --
-- @tparam string text The input string to complete. -- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed command. -- @tparam[opt] boolean add_space Whether to add a space after the completed command.

View File

@ -2,7 +2,7 @@
-- --
-- SPDX-License-Identifier: MPL-2.0 -- SPDX-License-Identifier: MPL-2.0
--[[- The @{cc.expect} library provides helper functions for verifying that --[[- The [`cc.expect`] library provides helper functions for verifying that
function arguments are well-formed and of the correct type. function arguments are well-formed and of the correct type.
@module cc.expect @module cc.expect

View File

@ -2,10 +2,10 @@
-- --
-- SPDX-License-Identifier: MPL-2.0 -- SPDX-License-Identifier: MPL-2.0
--- Read and draw nbt ("Nitrogen Fingers Text") images. --- Read and draw nft ("Nitrogen Fingers Text") images.
-- --
-- nft ("Nitrogen Fingers Text") is a file format for drawing basic images. -- nft ("Nitrogen Fingers Text") is a file format for drawing basic images.
-- Unlike the images that @{paintutils.parseImage} uses, nft supports coloured -- Unlike the images that [`paintutils.parseImage`] uses, nft supports coloured
-- text as well as simple coloured pixels. -- text as well as simple coloured pixels.
-- --
-- @module cc.image.nft -- @module cc.image.nft
@ -87,7 +87,7 @@ end
--- Draw an nft image to the screen. --- Draw an nft image to the screen.
-- --
-- @tparam table image An image, as returned from @{load} or @{draw}. -- @tparam table image An image, as returned from [`load`] or [`parse`].
-- @tparam number xPos The x position to start drawing at. -- @tparam number xPos The x position to start drawing at.
-- @tparam number xPos The y position to start drawing at. -- @tparam number xPos The y position to start drawing at.
-- @tparam[opt] term.Redirect target The terminal redirect to draw to. Defaults to the -- @tparam[opt] term.Redirect target The terminal redirect to draw to. Defaults to the

View File

@ -4,10 +4,9 @@
--[[- A pretty-printer for Lua errors. --[[- A pretty-printer for Lua errors.
:::warning > [!DANGER]
This is an internal module and SHOULD NOT be used in your own code. It may > This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time. > be removed or changed at any time.
:::
This consumes a list of messages and "annotations" and displays the error to the This consumes a list of messages and "annotations" and displays the error to the
terminal. terminal.
@ -100,7 +99,7 @@ local code_accent = pretty.text("\x95", colours.cyan)
over the underlying source, exposing the following functions: over the underlying source, exposing the following functions:
- `get_pos`: Get the line and column of an opaque position. - `get_pos`: Get the line and column of an opaque position.
- `get_line`: Get the source code for an opaque position. - `get_line`: Get the source code for an opaque position.
@tparam table message The message to display, as produced by @{cc.internal.syntax.errors}. @tparam table message The message to display, as produced by [`cc.internal.syntax.errors`].
]] ]]
return function(context, message) return function(context, message)
expect(1, context, "table") expect(1, context, "table")

View File

@ -4,10 +4,9 @@
--[[- Internal tools for working with errors. --[[- Internal tools for working with errors.
:::warning > [!DANGER]
This is an internal module and SHOULD NOT be used in your own code. It may > This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time. > be removed or changed at any time.
:::
@local @local
]] ]]

View File

@ -2,12 +2,11 @@
-- --
-- SPDX-License-Identifier: MPL-2.0 -- SPDX-License-Identifier: MPL-2.0
--[[- Upload a list of files, as received by the @{event!file_transfer} event. --[[- Upload a list of files, as received by the [`event!file_transfer`] event.
:::warning > [!DANGER]
This is an internal module and SHOULD NOT be used in your own code. It may > This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time. > be removed or changed at any time.
:::
@local @local
]] ]]

View File

@ -4,14 +4,13 @@
--[[- The error messages reported by our lexer and parser. --[[- The error messages reported by our lexer and parser.
:::warning > [!DANGER]
This is an internal module and SHOULD NOT be used in your own code. It may > This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time. > be removed or changed at any time.
:::
This provides a list of factory methods which take source positions and produce This provides a list of factory methods which take source positions and produce
appropriate error messages targeting that location. These error messages can appropriate error messages targeting that location. These error messages can
then be displayed to the user via @{cc.internal.error_printer}. then be displayed to the user via [`cc.internal.error_printer`].
@local @local
]] ]]
@ -121,7 +120,7 @@ function errors.unfinished_string(start_pos, end_pos, quote)
end end
--[[- A string which ends with an escape sequence (so a literal `"foo\`). This --[[- A string which ends with an escape sequence (so a literal `"foo\`). This
is slightly different from @{unfinished_string}, as we don't want to suggest is slightly different from [`unfinished_string`], as we don't want to suggest
adding a quote. adding a quote.
@tparam number start_pos The start position of the string. @tparam number start_pos The start position of the string.
@ -467,7 +466,7 @@ function errors.standalone_name(pos)
} }
end end
--[[- A statement of the form `x.y`. This is similar to @{standalone_name}, but --[[- A statement of the form `x.y`. This is similar to [`standalone_name`], but
when the next token is on another line. when the next token is on another line.
@tparam number pos The position right after this name. @tparam number pos The position right after this name.

View File

@ -4,10 +4,9 @@
--[[- The main entrypoint to our Lua parser --[[- The main entrypoint to our Lua parser
:::warning > [!DANGER]
This is an internal module and SHOULD NOT be used in your own code. It may > This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time. > be removed or changed at any time.
:::
@local @local
]] ]]
@ -21,6 +20,8 @@ local error_printer = require "cc.internal.error_printer"
local error_sentinel = {} local error_sentinel = {}
local function make_context(input) local function make_context(input)
expect(1, input, "string")
local context = {} local context = {}
local lines = { 1 } local lines = { 1 }
@ -73,8 +74,9 @@ local function parse(input, start_symbol)
expect(2, start_symbol, "number") expect(2, start_symbol, "number")
local context = make_context(input) local context = make_context(input)
function context.report(msg) function context.report(msg, ...)
expect(1, msg, "table") expect(1, msg, "table", "function")
if type(msg) == "function" then msg = msg(...) end
error_printer(context, msg) error_printer(context, msg)
error(error_sentinel) error(error_sentinel)
end end
@ -110,8 +112,9 @@ local function parse_repl(input)
local context = make_context(input) local context = make_context(input)
local last_error = nil local last_error = nil
function context.report(msg) function context.report(msg, ...)
expect(1, msg, "table") expect(1, msg, "table", "function")
if type(msg) == "function" then msg = msg(...) end
last_error = msg last_error = msg
error(error_sentinel) error(error_sentinel)
end end

View File

@ -4,17 +4,16 @@
--[[- A lexer for Lua source code. --[[- A lexer for Lua source code.
:::warning > [!DANGER]
This is an internal module and SHOULD NOT be used in your own code. It may > This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time. > be removed or changed at any time.
:::
This module provides utilities for lexing Lua code, returning tokens compatible This module provides utilities for lexing Lua code, returning tokens compatible
with @{cc.internal.syntax.parser}. While all lexers are roughly the same, there with [`cc.internal.syntax.parser`]. While all lexers are roughly the same, there
are some design choices worth drawing attention to: are some design choices worth drawing attention to:
- The lexer uses Lua patterns (i.e. @{string.find}) as much as possible, - The lexer uses Lua patterns (i.e. [`string.find`]) as much as possible,
trying to avoid @{string.sub} loops except when needed. This allows us to trying to avoid [`string.sub`] loops except when needed. This allows us to
move string processing to native code, which ends up being much faster. move string processing to native code, which ends up being much faster.
- We try to avoid allocating where possible. There are some cases we need to - We try to avoid allocating where possible. There are some cases we need to
@ -96,7 +95,7 @@ local function lex_number(context, str, start)
local contents = sub(str, start, pos - 1) local contents = sub(str, start, pos - 1)
if not tonumber(contents) then if not tonumber(contents) then
-- TODO: Separate error for "2..3"? -- TODO: Separate error for "2..3"?
context.report(errors.malformed_number(start, pos - 1)) context.report(errors.malformed_number, start, pos - 1)
end end
return tokens.NUMBER, pos - 1 return tokens.NUMBER, pos - 1
@ -118,14 +117,14 @@ local function lex_string(context, str, start_pos, quote)
return tokens.STRING, pos return tokens.STRING, pos
elseif c == "\n" or c == "\r" or c == "" then elseif c == "\n" or c == "\r" or c == "" then
-- We don't call newline here, as that's done for the next token. -- We don't call newline here, as that's done for the next token.
context.report(errors.unfinished_string(start_pos, pos, quote)) context.report(errors.unfinished_string, start_pos, pos, quote)
return tokens.STRING, pos - 1 return tokens.STRING, pos - 1
elseif c == "\\" then elseif c == "\\" then
c = sub(str, pos + 1, pos + 1) c = sub(str, pos + 1, pos + 1)
if c == "\n" or c == "\r" then if c == "\n" or c == "\r" then
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
elseif c == "z" then elseif c == "z" then
pos = pos + 2 pos = pos + 2
@ -133,7 +132,7 @@ local function lex_string(context, str, start_pos, quote)
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
end end
@ -178,7 +177,7 @@ end
-- @tparam number start The start position, after the input boundary. -- @tparam number start The start position, after the input boundary.
-- @tparam number len The expected length of the boundary. Equal to 1 + the -- @tparam number len The expected length of the boundary. Equal to 1 + the
-- number of `=`. -- number of `=`.
-- @treturn number|nil The end position, or @{nil} if this is not terminated. -- @treturn number|nil The end position, or [`nil`] if this is not terminated.
local function lex_long_str(context, str, start, len) local function lex_long_str(context, str, start, len)
local pos = start local pos = start
while true do while true do
@ -196,7 +195,7 @@ local function lex_long_str(context, str, start, len)
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 and boundary_pos - pos == len and len == 1 then if ok and boundary_pos - pos == len and len == 1 then
context.report(errors.nested_long_str(pos, boundary_pos)) context.report(errors.nested_long_str, pos, boundary_pos)
end end
pos = boundary_pos pos = boundary_pos
@ -238,12 +237,12 @@ local function lex_token(context, str, pos)
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - pos) local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - pos)
if end_pos then return tokens.STRING, end_pos end if end_pos then return tokens.STRING, end_pos end
context.report(errors.unfinished_long_string(pos, boundary_pos, boundary_pos - pos)) context.report(errors.unfinished_long_string, pos, boundary_pos, boundary_pos - pos)
return tokens.ERROR, #str return tokens.ERROR, #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 "[="
context.report(errors.malformed_long_string(pos, boundary_pos, boundary_pos - pos)) context.report(errors.malformed_long_string, pos, boundary_pos, boundary_pos - pos)
return tokens.ERROR, boundary_pos return tokens.ERROR, boundary_pos
end end
@ -260,7 +259,7 @@ local function lex_token(context, str, pos)
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - comment_pos) local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - comment_pos)
if end_pos then return tokens.COMMENT, end_pos end if end_pos then return tokens.COMMENT, end_pos end
context.report(errors.unfinished_long_comment(pos, boundary_pos, boundary_pos - comment_pos)) context.report(errors.unfinished_long_comment, pos, boundary_pos, boundary_pos - comment_pos)
return tokens.ERROR, #str return tokens.ERROR, #str
end end
end end
@ -317,18 +316,18 @@ local function lex_token(context, str, pos)
if end_pos - pos <= 3 then if end_pos - pos <= 3 then
local contents = sub(str, pos, end_pos) local contents = sub(str, pos, end_pos)
if contents == "&&" then if contents == "&&" then
context.report(errors.wrong_and(pos, end_pos)) context.report(errors.wrong_and, pos, end_pos)
return tokens.AND, end_pos return tokens.AND, end_pos
elseif contents == "||" then elseif contents == "||" then
context.report(errors.wrong_or(pos, end_pos)) context.report(errors.wrong_or, pos, end_pos)
return tokens.OR, end_pos return tokens.OR, end_pos
elseif contents == "!=" or contents == "<>" then elseif contents == "!=" or contents == "<>" then
context.report(errors.wrong_ne(pos, end_pos)) context.report(errors.wrong_ne, pos, end_pos)
return tokens.NE, end_pos return tokens.NE, end_pos
end end
end end
context.report(errors.unexpected_character(pos)) context.report(errors.unexpected_character, pos)
return tokens.ERROR, end_pos return tokens.ERROR, end_pos
end end
end end

View File

@ -4,10 +4,9 @@
--[[- A parser for Lua programs and expressions. --[[- A parser for Lua programs and expressions.
:::warning > [!DANGER]
This is an internal module and SHOULD NOT be used in your own code. It may > This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time. > be removed or changed at any time.
:::
Most of the code in this module is automatically generated from the Lua grammar, Most of the code in this module is automatically generated from the Lua grammar,
hence being mostly unreadable! hence being mostly unreadable!
@ -244,7 +243,7 @@ local function handle_error(context, stack, stack_n, token, token_start, token_e
end end
end end
context.report(errors.unexpected_token(token, token_start, token_end)) context.report(errors.unexpected_token, token, token_start, token_end)
return false return false
end end

View File

@ -5,12 +5,12 @@
--[[- A pretty printer for rendering data structures in an aesthetically --[[- A pretty printer for rendering data structures in an aesthetically
pleasing manner. pleasing manner.
In order to display something using @{cc.pretty}, you build up a series of In order to display something using [`cc.pretty`], you build up a series of
@{Doc|documents}. These behave a little bit like strings; you can concatenate [documents][`Doc`]. These behave a little bit like strings; you can concatenate
them together and then print them to the screen. them together and then print them to the screen.
However, documents also allow you to control how they should be printed. There However, documents also allow you to control how they should be printed. There
are several functions (such as @{nest} and @{group}) which allow you to control are several functions (such as [`nest`] and [`group`]) which allow you to control
the "layout" of the document. When you come to display the document, the 'best' the "layout" of the document. When you come to display the document, the 'best'
(most compact) layout is used. (most compact) layout is used.
@ -37,7 +37,7 @@ local expect, field = expect.expect, expect.field
local type, getmetatable, setmetatable, colours, str_write, tostring = type, getmetatable, setmetatable, colours, write, tostring local type, getmetatable, setmetatable, colours, str_write, tostring = type, getmetatable, setmetatable, colours, write, tostring
local debug_info, debug_local = debug.getinfo, debug.getlocal local debug_info, debug_local = debug.getinfo, debug.getlocal
--- @{table.insert} alternative, but with the length stored inline. --- [`table.insert`] alternative, but with the length stored inline.
local function append(out, value) local function append(out, value)
local n = out.n + 1 local n = out.n + 1
out[n], out.n = value, n out[n], out.n = value, n
@ -59,10 +59,10 @@ local empty = mk_doc({ tag = "nil" })
--- A document with a single space in it. --- A document with a single space in it.
local space = mk_doc({ tag = "text", text = " " }) local space = mk_doc({ tag = "text", text = " " })
--- A line break. When collapsed with @{group}, this will be replaced with @{empty}. --- A line break. When collapsed with [`group`], this will be replaced with [`empty`].
local line = mk_doc({ tag = "line", flat = empty }) local line = mk_doc({ tag = "line", flat = empty })
--- A line break. When collapsed with @{group}, this will be replaced with @{space}. --- A line break. When collapsed with [`group`], this will be replaced with [`space`].
local space_line = mk_doc({ tag = "line", flat = space }) local space_line = mk_doc({ tag = "line", flat = space })
local text_cache = { [""] = empty, [" "] = space, ["\n"] = space_line } local text_cache = { [""] = empty, [" "] = space, ["\n"] = space_line }
@ -73,7 +73,7 @@ end
--- Create a new document from a string. --- Create a new document from a string.
-- --
-- If your string contains multiple lines, @{group} will flatten the string -- If your string contains multiple lines, [`group`] will flatten the string
-- into a single line, with spaces between each line. -- into a single line, with spaces between each line.
-- --
-- @tparam string text The string to construct a new document with. -- @tparam string text The string to construct a new document with.
@ -453,7 +453,7 @@ end
--- Pretty-print an arbitrary object, converting it into a document. --- Pretty-print an arbitrary object, converting it into a document.
-- --
-- This can then be rendered with @{write} or @{print}. -- This can then be rendered with [`write`] or [`print`].
-- --
-- @param obj The object to pretty-print. -- @param obj The object to pretty-print.
-- @tparam[opt] { function_args = boolean, function_source = boolean } options -- @tparam[opt] { function_args = boolean, function_source = boolean } options
@ -479,7 +479,7 @@ local function pretty(obj, options)
return pretty_impl(obj, actual_options, {}) return pretty_impl(obj, actual_options, {})
end end
--[[- A shortcut for calling @{pretty} and @{print} together. --[[- A shortcut for calling [`pretty`] and [`print`] together.
@param obj The object to pretty-print. @param obj The object to pretty-print.
@tparam[opt] { function_args = boolean, function_source = boolean } options @tparam[opt] { function_args = boolean, function_source = boolean } options

View File

@ -2,8 +2,8 @@
-- --
-- SPDX-License-Identifier: LicenseRef-CCPL -- SPDX-License-Identifier: LicenseRef-CCPL
--[[- A pure Lua implementation of the builtin @{require} function and --[[- A pure Lua implementation of the builtin [`require`] function and
@{package} library. [`package`] library.
Generally you do not need to use this module - it is injected into the every Generally you do not need to use this module - it is injected into the every
program's environment. However, it may be useful when building a custom shell or program's environment. However, it may be useful when building a custom shell or
@ -11,7 +11,7 @@ when running programs yourself.
@module cc.require @module cc.require
@since 1.88.0 @since 1.88.0
@see using_require For an introduction on how to use @{require}. @see using_require For an introduction on how to use [`require`].
@usage Construct the package and require function, and insert them into a @usage Construct the package and require function, and insert them into a
custom environment. custom environment.
@ -110,13 +110,13 @@ local function make_require(package)
end end
end end
--- Build an implementation of Lua's @{package} library, and a @{require} --- Build an implementation of Lua's [`package`] library, and a [`require`]
-- function to load modules within it. -- function to load modules within it.
-- --
-- @tparam table env The environment to load packages into. -- @tparam table env The environment to load packages into.
-- @tparam string dir The directory that relative packages are loaded from. -- @tparam string dir The directory that relative packages are loaded from.
-- @treturn function The new @{require} function. -- @treturn function The new [`require`] function.
-- @treturn table The new @{package} library. -- @treturn table The new [`package`] library.
local function make_package(env, dir) local function make_package(env, dir)
expect(1, env, "table") expect(1, env, "table")
expect(2, dir, "string") expect(2, dir, "string")

View File

@ -4,16 +4,16 @@
--[[- A collection of helper methods for working with shell completion. --[[- A collection of helper methods for working with shell completion.
Most programs may be completed using the @{build} helper method, rather than Most programs may be completed using the [`build`] helper method, rather than
manually switching on the argument index. manually switching on the argument index.
Note, the helper functions within this module do not accept an argument index, Note, the helper functions within this module do not accept an argument index,
and so are not directly usable with the @{shell.setCompletionFunction}. Instead, and so are not directly usable with the [`shell.setCompletionFunction`]. Instead,
wrap them using @{build}, or your own custom function. wrap them using [`build`], or your own custom function.
@module cc.shell.completion @module cc.shell.completion
@since 1.85.0 @since 1.85.0
@see cc.completion For more general helpers, suitable for use with @{_G.read}. @see cc.completion For more general helpers, suitable for use with [`_G.read`].
@see shell.setCompletionFunction @see shell.setCompletionFunction
@usage Register a completion handler for example.lua which prompts for a @usage Register a completion handler for example.lua which prompts for a
@ -135,15 +135,15 @@ end
--[[- A helper function for building shell completion arguments. --[[- A helper function for building shell completion arguments.
This accepts a series of single-argument completion functions, and combines This accepts a series of single-argument completion functions, and combines
them into a function suitable for use with @{shell.setCompletionFunction}. them into a function suitable for use with [`shell.setCompletionFunction`].
@tparam nil|table|function ... Every argument to @{build} represents an argument @tparam nil|table|function ... Every argument to [`build`] represents an argument
to the program you wish to complete. Each argument can be one of three types: to the program you wish to complete. Each argument can be one of three types:
- `nil`: This argument will not be completed. - `nil`: This argument will not be completed.
- A function: This argument will be completed with the given function. It is - A function: This argument will be completed with the given function. It is
called with the @{shell} object, the string to complete and the arguments called with the [`shell`] object, the string to complete and the arguments
before this one. before this one.
- A table: This acts as a more powerful version of the function case. The table - A table: This acts as a more powerful version of the function case. The table
@ -197,12 +197,12 @@ return {
programWithArgs = programWithArgs, programWithArgs = programWithArgs,
-- Re-export various other functions -- Re-export various other functions
help = wrap(help.completeTopic), --- Wraps @{help.completeTopic} as a @{build} compatible function. help = wrap(help.completeTopic), --- Wraps [`help.completeTopic`] as a [`build`] compatible function.
choice = wrap(completion.choice), --- Wraps @{cc.completion.choice} as a @{build} compatible function. choice = wrap(completion.choice), --- Wraps [`cc.completion.choice`] as a [`build`] compatible function.
peripheral = wrap(completion.peripheral), --- Wraps @{cc.completion.peripheral} as a @{build} compatible function. peripheral = wrap(completion.peripheral), --- Wraps [`cc.completion.peripheral`] as a [`build`] compatible function.
side = wrap(completion.side), --- Wraps @{cc.completion.side} as a @{build} compatible function. side = wrap(completion.side), --- Wraps [`cc.completion.side`] as a [`build`] compatible function.
setting = wrap(completion.setting), --- Wraps @{cc.completion.setting} as a @{build} compatible function. setting = wrap(completion.setting), --- Wraps [`cc.completion.setting`] as a [`build`] compatible function.
command = wrap(completion.command), --- Wraps @{cc.completion.command} as a @{build} compatible function. command = wrap(completion.command), --- Wraps [`cc.completion.command`] as a [`build`] compatible function.
build = build, build = build,
} }

View File

@ -13,7 +13,7 @@ local expect = (require and require("cc.expect") or dofile("rom/modules/main/cc/
--[[- Wraps a block of text, so that each line fits within the given width. --[[- Wraps a block of text, so that each line fits within the given width.
This may be useful if you want to wrap text before displaying it to a This may be useful if you want to wrap text before displaying it to a
@{monitor} or @{printer} without using @{_G.print|print}. [`monitor`] or [`printer`] without using [print][`_G.print`].
@tparam string text The string to wrap. @tparam string text The string to wrap.
@tparam[opt] number width The width to constrain to, defaults to the width of @tparam[opt] number width The width to constrain to, defaults to the width of

View File

@ -6,17 +6,17 @@
-- --
-- When multiple programs are running, it displays a tab bar at the top of the -- When multiple programs are running, it displays a tab bar at the top of the
-- screen, which allows you to switch between programs. New programs can be -- screen, which allows you to switch between programs. New programs can be
-- launched using the `fg` or `bg` programs, or using the @{shell.openTab} and -- launched using the `fg` or `bg` programs, or using the [`shell.openTab`] and
-- @{multishell.launch} functions. -- [`multishell.launch`] functions.
-- --
-- Each process is identified by its ID, which corresponds to its position in -- Each process is identified by its ID, which corresponds to its position in
-- the tab list. As tabs may be opened and closed, this ID is _not_ constant -- the tab list. As tabs may be opened and closed, this ID is _not_ constant
-- over a program's run. As such, be careful not to use stale IDs. -- over a program's run. As such, be careful not to use stale IDs.
-- --
-- As with @{shell}, @{multishell} is not a "true" API. Instead, it is a -- As with [`shell`], [`multishell`] is not a "true" API. Instead, it is a
-- standard program, which launches a shell and injects its API into the shell's -- standard program, which launches a shell and injects its API into the shell's
-- environment. This API is not available in the global environment, and so is -- environment. This API is not available in the global environment, and so is
-- not available to @{os.loadAPI|APIs}. -- not available to [APIs][`os.loadAPI`].
-- --
-- @module[module] multishell -- @module[module] multishell
-- @since 1.6 -- @since 1.6
@ -222,7 +222,7 @@ local multishell = {} --- @export
--- Get the currently visible process. This will be the one selected on --- Get the currently visible process. This will be the one selected on
-- the tab bar. -- the tab bar.
-- --
-- Note, this is different to @{getCurrent}, which returns the process which is -- Note, this is different to [`getCurrent`], which returns the process which is
-- currently executing. -- currently executing.
-- --
-- @treturn number The currently visible process's index. -- @treturn number The currently visible process's index.
@ -235,7 +235,7 @@ end
-- --
-- @tparam number n The process index to switch to. -- @tparam number n The process index to switch to.
-- @treturn boolean If the process was changed successfully. This will -- @treturn boolean If the process was changed successfully. This will
-- return @{false} if there is no process with this id. -- return [`false`] if there is no process with this id.
-- @see getFocus -- @see getFocus
function multishell.setFocus(n) function multishell.setFocus(n)
expect(1, n, "number") expect(1, n, "number")
@ -250,9 +250,9 @@ end
--- Get the title of the given tab. --- Get the title of the given tab.
-- --
-- This starts as the name of the program, but may be changed using -- This starts as the name of the program, but may be changed using
-- @{multishell.setTitle}. -- [`multishell.setTitle`].
-- @tparam number n The process index. -- @tparam number n The process index.
-- @treturn string|nil The current process title, or @{nil} if the -- @treturn string|nil The current process title, or [`nil`] if the
-- process doesn't exist. -- process doesn't exist.
function multishell.getTitle(n) function multishell.getTitle(n)
expect(1, n, "number") expect(1, n, "number")

View File

@ -275,6 +275,12 @@ local function drawCanvas()
end end
end end
local function termResize()
w, h = term.getSize()
drawCanvas()
drawInterface()
end
local menu_choices = { local menu_choices = {
Save = function() Save = function()
if bReadOnly then if bReadOnly then
@ -376,6 +382,8 @@ local function accessMenu()
nMenuPosEnd = nMenuPosEnd + 1 nMenuPosEnd = nMenuPosEnd + 1
nMenuPosStart = nMenuPosEnd nMenuPosStart = nMenuPosEnd
end end
elseif id == "term_resize" then
termResize()
end end
end end
end end
@ -434,9 +442,7 @@ local function handleEvents()
drawInterface() drawInterface()
end end
elseif id == "term_resize" then elseif id == "term_resize" then
w, h = term.getSize() termResize()
drawCanvas()
drawInterface()
end end
end end
end end

View File

@ -56,14 +56,6 @@ local function get(url)
) )
if response then if response then
-- If spam protection is activated, we get redirected to /paste with Content-Type: text/html
local headers = response.getResponseHeaders()
if not headers["Content-Type"] or not headers["Content-Type"]:find("^text/plain") then
io.stderr:write("Failed.\n")
print("Pastebin blocked the download due to spam protection. Please complete the captcha in a web browser: https://pastebin.com/" .. textutils.urlEncode(paste))
return
end
print("Success.") print("Success.")
local sResponse = response.readAll() local sResponse = response.readAll()

View File

@ -36,13 +36,6 @@ local function getFilename(sUrl)
end end
local function get(sUrl) local function get(sUrl)
-- Check if the URL is valid
local ok, err = http.checkURL(url)
if not ok then
printError(err or "Invalid URL.")
return
end
write("Connecting to " .. sUrl .. "... ") write("Connecting to " .. sUrl .. "... ")
local response = http.get(sUrl , nil , true) local response = http.get(sUrl , nil , true)

View File

@ -25,21 +25,13 @@ local tEnv = {
} }
setmetatable(tEnv, { __index = _ENV }) setmetatable(tEnv, { __index = _ENV })
-- Replace our package.path, so that it loads from the current directory, rather -- Replace our require with new instance that loads from the current directory
-- than from /rom/programs. This makes it a little more friendly to use and -- rather than from /rom/programs. This makes it more friendly to use and closer
-- closer to what you'd expect. -- to what you'd expect.
do do
local make_package = require "cc.require".make
local dir = shell.dir() local dir = shell.dir()
if dir:sub(1, 1) ~= "/" then dir = "/" .. dir end _ENV.require, _ENV.package = make_package(_ENV, dir)
if dir:sub(-1) ~= "/" then dir = dir .. "/" end
local strip_path = "?;?.lua;?/init.lua;"
local path = package.path
if path:sub(1, #strip_path) == strip_path then
path = path:sub(#strip_path + 1)
end
package.path = dir .. "?;" .. dir .. "?.lua;" .. dir .. "?/init.lua;" .. path
end end
if term.isColour() then if term.isColour() then
@ -82,18 +74,13 @@ while running do
local name, offset = "=lua[" .. chunk_idx .. "]", 0 local name, offset = "=lua[" .. chunk_idx .. "]", 0
local force_print = 0
local func, err = load(input, name, "t", tEnv) local func, err = load(input, name, "t", tEnv)
if load("return " .. input) then
local expr_func = load("return _echo(" .. input .. ");", name, "t", tEnv) -- We wrap the expression with a call to _echo(...), which prevents tail
if not func then -- calls (and thus confusing errors). Note we check this is a valid
if expr_func then -- expression separately, to avoid accepting inputs like `)--` (which are
func = expr_func -- parsed as `_echo()--)`.
offset = 13 func = load("return _echo(" .. input .. "\n)", name, "t", tEnv)
force_print = 1
end
elseif expr_func then
func = expr_func
offset = 13 offset = 13
end end
@ -103,9 +90,8 @@ while running do
local results = table.pack(exception.try(func)) local results = table.pack(exception.try(func))
if results[1] then if results[1] then
local n = 1 for i = 2, results.n do
while n < results.n or n <= force_print do local value = results[i]
local value = results[n + 1]
local ok, serialised = pcall(pretty.pretty, value, { local ok, serialised = pcall(pretty.pretty, value, {
function_args = settings.get("lua.function_args"), function_args = settings.get("lua.function_args"),
function_source = settings.get("lua.function_source"), function_source = settings.get("lua.function_source"),
@ -115,7 +101,6 @@ while running do
else else
print(tostring(value)) print(tostring(value))
end end
n = n + 1
end end
else else
printError(results[2]) printError(results[2])

View File

@ -4,29 +4,29 @@
--[[- The shell API provides access to CraftOS's command line interface. --[[- The shell API provides access to CraftOS's command line interface.
It allows you to @{run|start programs}, @{setCompletionFunction|add completion It allows you to [start programs][`run`], [add completion for a
for a program}, and much more. program][`setCompletionFunction`], and much more.
@{shell} is not a "true" API. Instead, it is a standard program, which injects [`shell`] is not a "true" API. Instead, it is a standard program, which injects
its API into the programs that it launches. This allows for multiple shells to its API into the programs that it launches. This allows for multiple shells to
run at the same time, but means that the API is not available in the global run at the same time, but means that the API is not available in the global
environment, and so is unavailable to other @{os.loadAPI|APIs}. environment, and so is unavailable to other [APIs][`os.loadAPI`].
## Programs and the program path ## Programs and the program path
When you run a command with the shell, either from the prompt or When you run a command with the shell, either from the prompt or
@{shell.run|from Lua code}, the shell API performs several steps to work out [from Lua code][`shell.run`], the shell API performs several steps to work out
which program to run: which program to run:
1. Firstly, the shell attempts to resolve @{shell.aliases|aliases}. This allows 1. Firstly, the shell attempts to resolve [aliases][`shell.aliases`]. This allows
us to use multiple names for a single command. For example, the `list` us to use multiple names for a single command. For example, the `list`
program has two aliases: `ls` and `dir`. When you write `ls /rom`, that's program has two aliases: `ls` and `dir`. When you write `ls /rom`, that's
expanded to `list /rom`. expanded to `list /rom`.
2. Next, the shell attempts to find where the program actually is. For this, it 2. Next, the shell attempts to find where the program actually is. For this, it
uses the @{shell.path|program path}. This is a colon separated list of uses the [program path][`shell.path`]. This is a colon separated list of
directories, each of which is checked to see if it contains the program. directories, each of which is checked to see if it contains the program.
`list` or `list.lua` doesn't exist in `.` (the current directory), so she `list` or `list.lua` doesn't exist in `.` (the current directory), so the
shell now looks in `/rom/programs`, where `list.lua` can be found! shell now looks in `/rom/programs`, where `list.lua` can be found!
3. Finally, the shell reads the file and checks if the file starts with a 3. Finally, the shell reads the file and checks if the file starts with a
@ -192,7 +192,7 @@ end
--- Run a program with the supplied arguments. --- Run a program with the supplied arguments.
-- --
-- Unlike @{shell.run}, each argument is passed to the program verbatim. While -- Unlike [`shell.run`], each argument is passed to the program verbatim. While
-- `shell.run("echo", "b c")` runs `echo` with `b` and `c`, -- `shell.run("echo", "b c")` runs `echo` with `b` and `c`,
-- `shell.execute("echo", "b c")` runs `echo` with a single argument `b c`. -- `shell.execute("echo", "b c")` runs `echo` with a single argument `b c`.
-- --
@ -276,7 +276,7 @@ function shell.exit()
end end
--- Return the current working directory. This is what is displayed before the --- Return the current working directory. This is what is displayed before the
-- `> ` of the shell prompt, and is used by @{shell.resolve} to handle relative -- `> ` of the shell prompt, and is used by [`shell.resolve`] to handle relative
-- paths. -- paths.
-- --
-- @treturn string The current working directory. -- @treturn string The current working directory.
@ -313,10 +313,10 @@ function shell.path()
return sPath return sPath
end end
--- Set the @{path|current program path}. --- Set the [current program path][`path`].
-- --
-- Be careful to prefix directories with a `/`. Otherwise they will be searched -- Be careful to prefix directories with a `/`. Otherwise they will be searched
-- for from the @{shell.dir|current directory}, rather than the computer's root. -- for from the [current directory][`shell.dir`], rather than the computer's root.
-- --
-- @tparam string path The new program path. -- @tparam string path The new program path.
-- @since 1.2 -- @since 1.2
@ -327,8 +327,8 @@ end
--- Resolve a relative path to an absolute path. --- Resolve a relative path to an absolute path.
-- --
-- The @{fs} and @{io} APIs work using absolute paths, and so we must convert -- The [`fs`] and [`io`] APIs work using absolute paths, and so we must convert
-- any paths relative to the @{dir|current directory} to absolute ones. This -- any paths relative to the [current directory][`dir`] to absolute ones. This
-- does nothing when the path starts with `/`. -- does nothing when the path starts with `/`.
-- --
-- @tparam string path The path to resolve. -- @tparam string path The path to resolve.
@ -357,10 +357,10 @@ local function pathWithExtension(_sPath, _sExt)
return _sPath .. "." .. _sExt return _sPath .. "." .. _sExt
end end
--- Resolve a program, using the @{path|program path} and list of @{aliases|aliases}. --- Resolve a program, using the [program path][`path`] and list of [aliases][`aliases`].
-- --
-- @tparam string command The name of the program -- @tparam string command The name of the program
-- @treturn string|nil The absolute path to the program, or @{nil} if it could -- @treturn string|nil The absolute path to the program, or [`nil`] if it could
-- not be found. -- not be found.
-- @since 1.2 -- @since 1.2
-- @changed 1.80pr1 Now searches for files with and without the `.lua` extension. -- @changed 1.80pr1 Now searches for files with and without the `.lua` extension.
@ -406,7 +406,7 @@ function shell.resolveProgram(command)
return nil return nil
end end
--- Return a list of all programs on the @{shell.path|path}. --- Return a list of all programs on the [path][`shell.path`].
-- --
-- @tparam[opt] boolean include_hidden Include hidden files. Namely, any which -- @tparam[opt] boolean include_hidden Include hidden files. Namely, any which
-- start with `.`. -- start with `.`.
@ -518,7 +518,7 @@ end
-- completed to `ls rom/`. -- completed to `ls rom/`.
-- --
-- Completion handlers for your program may be registered with -- Completion handlers for your program may be registered with
-- @{shell.setCompletionFunction}. -- [`shell.setCompletionFunction`].
-- --
-- @tparam string sLine The input to complete. -- @tparam string sLine The input to complete.
-- @treturn { string }|nil The list of possible completions. -- @treturn { string }|nil The list of possible completions.
@ -614,7 +614,7 @@ end
--- Get a table containing all completion functions. --- Get a table containing all completion functions.
-- --
-- This should only be needed when building custom shells. Use -- This should only be needed when building custom shells. Use
-- @{setCompletionFunction} to add a completion function. -- [`setCompletionFunction`] to add a completion function.
-- --
-- @treturn { [string] = { fnComplete = function } } A table mapping the -- @treturn { [string] = { fnComplete = function } } A table mapping the
-- absolute path of programs, to their completion functions. -- absolute path of programs, to their completion functions.
@ -676,12 +676,12 @@ function shell.aliases()
end end
if multishell then if multishell then
--- Open a new @{multishell} tab running a command. --- Open a new [`multishell`] tab running a command.
-- --
-- This behaves similarly to @{shell.run}, but instead returns the process -- This behaves similarly to [`shell.run`], but instead returns the process
-- index. -- index.
-- --
-- This function is only available if the @{multishell} API is. -- This function is only available if the [`multishell`] API is.
-- --
-- @tparam string ... The command line to run. -- @tparam string ... The command line to run.
-- @see shell.run -- @see shell.run
@ -706,7 +706,7 @@ if multishell then
end end
end end
--- Switch to the @{multishell} tab with the given index. --- Switch to the [`multishell`] tab with the given index.
-- --
-- @tparam number id The tab to switch to. -- @tparam number id The tab to switch to.
-- @see multishell.setFocus -- @see multishell.setFocus

2
vendor/Cobalt vendored

@ -1 +1 @@
Subproject commit a630403859ff6321d10292fbf94dcfb30b507d9d Subproject commit 6b0d2e234ebbfb1ffd20cde071ecd4c782be6da1