1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-18 07:27:39 +00:00

Compare commits

..

27 Commits

Author SHA1 Message Date
SquidDev
3ea2d6a0a8 Merge branch 'master' into mc-1.14.x 2019-10-04 19:56:49 +01:00
SquidDev
c802290437 Bump version 2019-10-04 19:52:02 +01:00
SquidDev
f7781defe5 Merge branch 'master' into mc-1.14.x 2019-10-04 19:44:34 +01:00
SquidDev
418420523a Proxy the current turtle's inventory
Previously we were just returning the current tile. However, if someone
was holding a reference to this inventory (such as a GUI), then it'd be
outdated and invalid once the turtle had moved.

This caused a couple of issues:
 - turtle_inventory events would not be fired when moving items in the
   turtle GUI.
 - As of 75e2845c01, turtles would no
   longer share their inventory state after moving. Thus, removing items
   from a GUI using an invalid inventory would move them from an old
   tile, duplicating the items.

Fixes #298, fixes #300
2019-10-04 16:53:48 +01:00
SquidDev
d342a1f368 Prevent wired modems dropping on block change
Fixes #303
2019-10-01 20:06:38 +01:00
SquidDev
81f85361d5 Move Lua files to the correct location
I really need to fix this.
2019-10-01 19:43:03 +01:00
SquidDev
f1621b30ec Remove redundant imports 2019-10-01 19:15:13 +01:00
SquidDev
d4f6a594b6 Merge branch 'master' into mc-1.14.x 2019-10-01 18:58:40 +01:00
SquidDev
ff5ba5c131 Update GUI code to be compatible with 1.14.4
Not quite sure when this changed, but I'm fairly sure isMouseOver wasn't
a thing when I wrote this. Or I'm a plonker. Both are possible.

Also fixes mouse dragging not being handled in turtles.

Fixes #299
2019-10-01 18:53:46 +01:00
SquidDev
4243f30308 Bump Forge version 2019-10-01 18:53:38 +01:00
Jonathan Coates
813e91073d Merge pull request #302 from Wendelstein7/master
Fixed turtle property category
2019-09-30 15:59:55 +01:00
Jonathan Coates
7250f22ff6 Update CI to also run on PRs 2019-09-30 15:37:52 +01:00
Wendelstein7
db31a53bba Fixed turtle property category
Likely was a leftover from copy pasting and a tired programmer.
2019-09-30 15:15:22 +02:00
SquidDev
3023f235a4 Goodbye Travis, I'm with GH Actions now 2019-09-27 08:56:53 +01:00
SquidDev
79cd8b4da5 Also return the number of affected entities
Closes #293. Doesn't really solve anything there aside from exposing the
number, but sadly there's not really anything obvious I can do on my end
- the command API just doesn't expose anything else.
2019-09-15 18:48:51 +01:00
Jonathan Coates
8e4d311cd9 Refactor shell completion into a separate module (#281)
- Adds cc.completions module, with a couple of helper functions for
   working with the more general completion functionality (i.e. that
   provided by read).
 - Adds cc.shell.completions module, which provides shell-specific
   completion functions.
 - Add a "program completion builder", which allows you to write stuff
   like this:

       shell.setCompletionFunction( "rom/programs/redstone.lua", 
         completion.build(
           { completion.choice, { "probe", "set ", "pulse " } },
           completion.side) )

Closes #232
2019-09-15 18:48:40 +01:00
SquidDev
9bd8c86a94 Change event priority to HIGHEST
See the comments in a802f25dd6 -
effectively we want to make sure we arrive before any other thing which
may capture items (some magnet mods, etc...).
2019-09-15 16:42:21 +01:00
Jonathan Coates
cbc0c1d0b6 A little experiment with GitHub actions
Let's give this a go.
2019-09-14 09:16:13 +01:00
SquidDev
49c37857d4 Window.reposition now allow changing the redirect buffer
See #270
2019-09-13 20:55:20 +01:00
SquidDev
b1139a4bf6 Bump to Forge RB 2019-09-12 21:09:57 +01:00
Jonathan Coates
7e8559278e Merge pull request #291 from parly/patch-1.14.x-1
Fix os.time() and os.day() behavior on 1.14
2019-08-19 15:34:20 +01:00
parly
1e7f1c98fc Fix os.time() and os.day() behavior 2019-08-19 23:04:57 +09:00
SquidDev
a802f25dd6 Do not listen to block/entity drop events
It appears several mods inject their own drops on the LOWEST priority,
meaning that we capture the existing drops, and the other mod will clear
the (now empty) drop list and add its own, resulting in dupe bugs.

While I'd argue it's somewhat dubious doing this on the LOWEST priority,
it's not a battle I'm prepared to fight. For now, we just remove the
block/entity drop handlers, and handle all drop logic when entities are
spawned.

Fixes #288
2019-08-19 10:33:53 +01:00
SquidDev
f1d6d21d6d Add back texture registration hook
I totally forgot to do this when Forge re-added this functionality.

Fixes #285
2019-08-18 16:12:16 +01:00
Jonathan Coates
a80302c513 Merge pull request #287 from Lignum/patch-1
The pettiest of spelling fixes
2019-08-15 06:59:59 +01:00
Lignum
1c46949da7 Available 2019-08-15 07:37:02 +02:00
SquidDev
46d78af068 Fix changelog being out-of-sync 2019-08-04 11:05:44 +01:00
32 changed files with 674 additions and 309 deletions

18
.github/workflows/main-ci.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: ./gradlew build --no-daemon

View File

@@ -1,14 +0,0 @@
language: java
script: ./gradlew build --no-daemon
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/s
jdk:
- openjdk8

View File

@@ -1,5 +1,5 @@
# ![CC: Tweaked](logo.png)
[![Current build status](https://travis-ci.org/SquidDev-CC/CC-Tweaked.svg?branch=master)](https://travis-ci.org/SquidDev-CC/CC-Tweaked "Current build status") [![Download CC: Tweaked on CurseForge](http://cf.way2muchnoise.eu/title/cc-tweaked.svg)](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge")
[![Current build status](https://github.com/SquidDev-CC/CC-Tweaked/workflows/Build/badge.svg)](https://github.com/SquidDev-CC/CC-Tweaked/actions "Current build status") [![Download CC: Tweaked on CurseForge](http://cf.way2muchnoise.eu/title/cc-tweaked.svg)](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge")
CC: Tweaked is a fork of [ComputerCraft](https://github.com/dan200/ComputerCraft), adding programmable computers,
turtles and more to Minecraft.

View File

@@ -9,7 +9,7 @@ buildscript {
}
dependencies {
classpath 'com.google.code.gson:gson:2.8.1'
classpath 'net.minecraftforge.gradle:ForgeGradle:3.0.134'
classpath 'net.minecraftforge.gradle:ForgeGradle:3.0.147'
classpath 'net.sf.proguard:proguard-gradle:6.1.0beta2'
classpath 'org.ajoberstar.grgit:grgit-gradle:3.0.0'
}
@@ -94,11 +94,11 @@ dependencies {
minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}"
compileOnly fg.deobf("mezz.jei:jei-1.14.3:6.0.0.7:api")
compileOnly fg.deobf("mezz.jei:jei-1.14.4:6.0.0.10:api")
// deobfProvided "pl.asie:Charset-Lib:0.5.4.6"
// deobfProvided "MCMultiPart2:MCMultiPart:2.5.3"
runtimeOnly fg.deobf("mezz.jei:jei-1.14.3:6.0.0.7")
runtimeOnly fg.deobf("mezz.jei:jei-1.14.4:6.0.0.10")
shade 'org.squiddev:Cobalt:0.5.0-SNAPSHOT'

View File

@@ -1,7 +1,7 @@
# Mod properties
mod_version=1.84.1
mod_version=1.85.0
# Minecraft properties
mc_version=1.14.4
forge_version=28.0.45
mappings_version=20190806-1.14.3
forge_version=28.1.26
mappings_version=20190912-1.14.3

View File

@@ -12,6 +12,7 @@ import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.media.items.ItemDisk;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.util.Colour;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.IUnbakedModel;
import net.minecraft.client.renderer.model.ModelResourceLocation;
@@ -58,8 +59,8 @@ public final class ClientRegistry
};
private static final String[] EXTRA_TEXTURES = new String[] {
// TODO: Gather these automatically from the model. I'm unable to get this working with Forge's current
// model loading code.
// TODO: Gather these automatically from the model. Sadly the model loader isn't available
// when stitching textures.
"block/turtle_colour",
"block/turtle_elf_overlay",
"block/turtle_crafty_face",
@@ -77,13 +78,12 @@ public final class ClientRegistry
@SubscribeEvent
public static void onTextureStitchEvent( TextureStitchEvent.Pre event )
{
/*
IResourceManager manager = Minecraft.getInstance().getResourceManager();
if( event.getMap() != Minecraft.getInstance().getTextureMap() ) return;
for( String extra : EXTRA_TEXTURES )
{
// TODO: event.getMap().registerSprite( manager, new ResourceLocation( ComputerCraft.MOD_ID, extra ) );
event.addSprite( new ResourceLocation( ComputerCraft.MOD_ID, extra ) );
}
*/
}
@SubscribeEvent

View File

@@ -176,11 +176,4 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Containe
return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY ))
|| super.mouseDragged( x, y, button, deltaX, deltaY );
}
@Override
public boolean mouseReleased( double x, double y, int button )
{
return (getFocused() != null && getFocused().mouseReleased( x, y, button ))
|| super.mouseReleased( x, y, button );
}
}

View File

@@ -129,4 +129,11 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
super.render( mouseX, mouseY, partialTicks );
renderHoveredToolTip( mouseX, mouseY );
}
@Override
public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
{
return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY ))
|| super.mouseDragged( x, y, button, deltaX, deltaY );
}
}

View File

@@ -241,11 +241,12 @@ public class WidgetTerminal implements IGuiEventListener
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
computer.mouseDrag( button + 1, charX + 1, charY + 1 );
lastMouseX = charX;
lastMouseY = charY;
lastMouseButton = button;
if( button == lastMouseButton && (charX != lastMouseX || charY != lastMouseY) )
{
computer.mouseDrag( button + 1, charX + 1, charY + 1 );
lastMouseX = charX;
lastMouseY = charY;
}
}
return false;
@@ -427,4 +428,10 @@ public class WidgetTerminal implements IGuiEventListener
ClientComputer computer = this.computer.get();
if( computer != null ) computer.queueEvent( event, args );
}
@Override
public boolean isMouseOver( double x, double y )
{
return true;
}
}

View File

@@ -95,4 +95,11 @@ public class WidgetWrapper implements IGuiEventListener
{
return height;
}
@Override
public boolean isMouseOver( double x, double y )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height;
}
}

View File

@@ -8,7 +8,6 @@ package dan200.computercraft.shared.common;
import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.network.container.HeldItemContainerData;
import dan200.computercraft.shared.util.InventoryUtil;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.container.Container;
@@ -33,7 +32,7 @@ public class ContainerHeldItem extends Container
super( type, id );
this.hand = hand;
stack = InventoryUtil.copyItem( player.getHeldItem( hand ) );
stack = player.getHeldItem( hand ).copy();
}
private static ContainerHeldItem createPrintout( int id, PlayerInventory inventory, HeldItemContainerData data )

View File

@@ -85,7 +85,7 @@ public class CommandAPI implements ILuaAPI
{
receiver.clearOutput();
int result = commandManager.handleCommand( m_computer.getSource(), command );
return new Object[] { result > 0, receiver.copyOutput() };
return new Object[] { result > 0, receiver.copyOutput(), result };
}
catch( Throwable t )
{

View File

@@ -311,13 +311,13 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
@Override
public double getTimeOfDay()
{
return (m_world.getGameTime() + 6000) % 24000 / 1000.0;
return (m_world.getDayTime() + 6000) % 24000 / 1000.0;
}
@Override
public int getDay()
{
return (int) ((m_world.getGameTime() + 6000) / 24000) + 1;
return (int) ((m_world.getDayTime() + 6000) / 24000) + 1;
}
@Override

View File

@@ -210,7 +210,7 @@ public class BlockCable extends BlockGeneric implements IWaterLoggable
BlockPos offsetPos = pos.offset( facing );
BlockState offsetState = world.getBlockState( offsetPos );
return Block.hasSolidSide( offsetState, world, offsetPos, facing.getOpposite() );
return hasSolidSide( offsetState, world, offsetPos, facing.getOpposite() );
}
@Nullable

View File

@@ -201,8 +201,7 @@ public class TileCable extends TileGeneric implements IPeripheralTile
public void onNeighbourChange( @Nonnull BlockPos neighbour )
{
Direction dir = getDirection();
if( neighbour.equals( getPos().offset( dir ) ) && hasModem()
&& getBlockState().isValidPosition( world, pos ) )
if( neighbour.equals( getPos().offset( dir ) ) && hasModem() && !getBlockState().isValidPosition( getWorld(), getPos() ) )
{
if( hasCable() )
{

View File

@@ -12,8 +12,7 @@ import dan200.computercraft.api.turtle.event.TurtleRefuelEvent;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.FurnaceTileEntity;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@@ -52,15 +51,9 @@ public final class FurnaceRefuelHandler implements TurtleRefuelEvent.Handler
return fuelToGive;
}
private static int getFuelPerItem( @Nonnull ItemStack stack )
{
int basicBurnTime = stack.getBurnTime();
int burnTime = ForgeEventFactory.getItemBurnTime(
stack,
basicBurnTime == -1 ? FurnaceTileEntity.getBurnTimes().getOrDefault( stack.getItem(), 0 ) : basicBurnTime
);
return (burnTime * 5) / 100;
return (ForgeHooks.getBurnTime( stack ) * 5) / 100;
}
@SubscribeEvent

View File

@@ -237,7 +237,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
m_inventoryChanged = false;
for( int n = 0; n < getSizeInventory(); n++ )
{
m_previousInventory.set( n, InventoryUtil.copyItem( getStackInSlot( n ) ) );
m_previousInventory.set( n, getStackInSlot( n ).copy() );
}
}
}
@@ -286,7 +286,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
if( slot < getSizeInventory() )
{
m_inventory.set( slot, ItemStack.read( tag ) );
m_previousInventory.set( slot, InventoryUtil.copyItem( m_inventory.get( slot ) ) );
m_previousInventory.set( slot, m_inventory.get( slot ).copy() );
}
}

View File

@@ -23,6 +23,7 @@ import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Holiday;
import dan200.computercraft.shared.util.HolidayUtil;
import dan200.computercraft.shared.util.InventoryDelegate;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
@@ -43,6 +44,7 @@ import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.wrapper.InvWrapper;
import javax.annotation.Nonnull;
import java.util.*;
@@ -68,6 +70,9 @@ public class TurtleBrain implements ITurtleAccess
private ComputerProxy m_proxy;
private GameProfile m_owningPlayer;
private final IInventory m_inventory = (InventoryDelegate) () -> m_owner;
private final IItemHandlerModifiable m_inventoryWrapper = new InvWrapper( m_inventory );
private Queue<TurtleCommandQueueEntry> m_commandQueue = new ArrayDeque<>();
private int m_commandsIssued = 0;
@@ -439,14 +444,14 @@ public class TurtleBrain implements ITurtleAccess
@Override
public IInventory getInventory()
{
return m_owner;
return m_inventory;
}
@Nonnull
@Override
public IItemHandlerModifiable getItemHandler()
{
return m_owner.getItemHandler();
return m_inventoryWrapper;
}
@Override

View File

@@ -14,8 +14,6 @@ import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@@ -36,7 +34,6 @@ public final class DropConsumer
private static Function<ItemStack, ItemStack> dropConsumer;
private static List<ItemStack> remainingDrops;
private static WeakReference<World> dropWorld;
private static BlockPos dropPos;
private static AxisAlignedBB dropBounds;
private static WeakReference<Entity> dropEntity;
@@ -46,7 +43,6 @@ public final class DropConsumer
remainingDrops = new ArrayList<>();
dropEntity = new WeakReference<>( entity );
dropWorld = new WeakReference<>( entity.world );
dropPos = null;
dropBounds = new AxisAlignedBB( entity.getPosition() ).grow( 2, 2, 2 );
entity.captureDrops( new ArrayList<>() );
@@ -55,10 +51,9 @@ public final class DropConsumer
public static void set( World world, BlockPos pos, Function<ItemStack, ItemStack> consumer )
{
dropConsumer = consumer;
remainingDrops = new ArrayList<>();
remainingDrops = new ArrayList<>( 2 );
dropEntity = null;
dropWorld = new WeakReference<>( world );
dropPos = pos;
dropBounds = new AxisAlignedBB( pos ).grow( 2, 2, 2 );
}
@@ -83,7 +78,6 @@ public final class DropConsumer
remainingDrops = null;
dropEntity = null;
dropWorld = null;
dropPos = null;
dropBounds = null;
return remainingStacks;
@@ -95,34 +89,7 @@ public final class DropConsumer
if( !remaining.isEmpty() ) remainingDrops.add( remaining );
}
@SubscribeEvent( priority = EventPriority.LOWEST )
public static void onEntityLivingDrops( LivingDropsEvent event )
{
// Capture any mob drops for the current entity
if( dropEntity != null && event.getEntity() == dropEntity.get() )
{
Collection<ItemEntity> drops = event.getDrops();
for( ItemEntity entityItem : drops ) handleDrops( entityItem.getItem() );
drops.clear();
}
}
@SubscribeEvent( priority = EventPriority.LOWEST )
public static void onHarvestDrops( BlockEvent.HarvestDropsEvent event )
{
// Capture block drops for the current entity
if( dropWorld != null && dropWorld.get() == event.getWorld()
&& dropPos != null && dropPos.equals( event.getPos() ) )
{
for( ItemStack item : event.getDrops() )
{
if( event.getWorld().getRandom().nextFloat() < event.getDropChance() ) handleDrops( item );
}
event.getDrops().clear();
}
}
@SubscribeEvent( priority = EventPriority.LOWEST )
@SubscribeEvent( priority = EventPriority.HIGHEST )
public static void onEntitySpawn( EntityJoinWorldEvent event )
{
// Capture any nearby item spawns

View File

@@ -0,0 +1,120 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
import java.util.Set;
/**
* Provides a delegate over inventories.
*
* This may be used both on {@link net.minecraft.tileentity.TileEntity}s to redirect the inventory to another tile,
* and by other interfaces to have inventories which change their backing store.
*/
@FunctionalInterface
public interface InventoryDelegate extends IInventory
{
IInventory getInventory();
@Override
default int getSizeInventory()
{
return getInventory().getSizeInventory();
}
@Override
default boolean isEmpty()
{
return getInventory().isEmpty();
}
@Nonnull
@Override
default ItemStack getStackInSlot( int slot )
{
return getInventory().getStackInSlot( slot );
}
@Nonnull
@Override
default ItemStack decrStackSize( int slot, int count )
{
return getInventory().decrStackSize( slot, count );
}
@Nonnull
@Override
default ItemStack removeStackFromSlot( int slot )
{
return getInventory().removeStackFromSlot( slot );
}
@Override
default void setInventorySlotContents( int slot, ItemStack stack )
{
getInventory().setInventorySlotContents( slot, stack );
}
@Override
default int getInventoryStackLimit()
{
return getInventory().getInventoryStackLimit();
}
@Override
default void markDirty()
{
getInventory().markDirty();
}
@Override
default boolean isUsableByPlayer( @Nonnull PlayerEntity player )
{
return getInventory().isUsableByPlayer( player );
}
@Override
default void openInventory( @Nonnull PlayerEntity player )
{
getInventory().openInventory( player );
}
@Override
default void closeInventory( @Nonnull PlayerEntity player )
{
getInventory().closeInventory( player );
}
@Override
default boolean isItemValidForSlot( int slot, @Nonnull ItemStack stack )
{
return getInventory().isItemValidForSlot( slot, stack );
}
@Override
default void clear()
{
getInventory().clear();
}
@Override
default int count( @Nonnull Item stack )
{
return getInventory().count( stack );
}
@Override
default boolean hasAny( @Nonnull Set<Item> set )
{
return getInventory().hasAny( set );
}
}

View File

@@ -70,12 +70,6 @@ public final class InventoryUtil
return shareTagA.equals( shareTagB );
}
@Nonnull
public static ItemStack copyItem( @Nonnull ItemStack a )
{
return a.copy();
}
// Methods for finding inventories:
public static IItemHandler getInventory( World world, BlockPos pos, Direction side )

View File

@@ -431,16 +431,20 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
return nX, nY
end
function window.reposition( nNewX, nNewY, nNewWidth, nNewHeight )
function window.reposition( nNewX, nNewY, nNewWidth, nNewHeight, newParent )
if type(nNewX) ~= "number" then expect(1, nNewX, "number") end
if type(nNewY) ~= "number" then expect(2, nNewY, "number") end
if nNewWidth ~= nil or nNewHeight ~= nil then
expect(3, nNewWidth, "number")
expect(4, nNewHeight, "number")
end
if newParent ~= nil and type(newParent) ~= "table" then expect(5, newParent, "table") end
nX = nNewX
nY = nNewY
if newParent then parent = newParent end
if nNewWidth and nNewHeight then
local tNewLines = {}
createEmptyLines( nNewWidth )

View File

@@ -1,3 +1,16 @@
# New features in CC: Tweaked 1.85.0
* Window.reposition now allows changing the redirect buffer
* Add cc.completion and cc.shell.completion modules
* command.exec also returns the number of affected objects, when exposed by the game.
And several bug fixes:
* Change how turtle mining drops are handled, improving compatibility with some mods.
* Fix several GUI desyncs after a turtle moves.
* Fix os.day/os.time using the incorrect world time.
* Prevent wired modems dropping incorrectly.
* Fix mouse events not firing within the computer GUI.
# New features in CC: Tweaked 1.84.1
* Update to latest Forge

View File

@@ -1,5 +1,14 @@
New features in CC: Tweaked 1.84.1
New features in CC: Tweaked 1.85.0
* Update to latest Forge
* Window.reposition now allows changing the redirect buffer
* Add cc.completion and cc.shell.completion modules
* command.exec also returns the number of affected objects, when exposed by the game.
And several bug fixes:
* Change how turtle mining drops are handled, improving compatibility with some mods.
* Fix several GUI desyncs after a turtle moves.
* Fix os.day/os.time using the incorrect world time.
* Prevent wired modems dropping incorrectly.
* Fix mouse events not firing within the computer GUI.
Type "help changelog" to see the full version history.

View File

@@ -0,0 +1,105 @@
--- A collection of helper methods for working with input completion, such
-- as that require by @{read}.
--
-- @module craftos.completion
-- @see cc.shell.completion For additional helpers to use with
-- @{shell.setCompletionFunction}.
local expect = require "cc.expect".expect
local function choice_impl(text, choices, add_space)
local results = {}
for n = 1, #choices do
local option = choices[n]
if #option + (add_space and 1 or 0) > #text and option:sub(1, #text) == text then
local result = option:sub(#text + 1)
if add_space then
table.insert(results, result .. " ")
else
table.insert(results, result)
end
end
end
return results
end
--- Complete from a choice of one or more strings.
--
-- @tparam string text The input string to complete.
-- @tparam { string... } choices The list of choices to complete from.
-- @tparam[opt] boolean add_space Whether to add a space after the completed item.
-- @treturn { string... } A list of suffixes of matching strings.
-- @usage Call @{read}, completing the names of various animals.
--
-- local animals = { "dog", "cat", "lion", "unicorn" }
-- read(nil, nil, function(text) return choice(text, animals) end)
local function choice(text, choices, add_space)
expect(1, text, "string")
expect(2, choices, "table")
expect(3, add_space, "boolean", "nil")
return choice_impl(text, choices, add_space)
end
--- Complete the name of a currently attached peripheral.
--
-- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed name.
-- @treturn { string... } A list of suffixes of matching peripherals.
-- @usage read(nil, nil, peripheral)
local function peripheral_(text, add_space)
expect(1, text, "string")
expect(2, add_space, "boolean", "nil")
return choice_impl(text, peripheral.getNames(), add_space)
end
local sides = redstone.getSides()
--- Complete the side of a computer.
--
-- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed side.
-- @treturn { string... } A list of suffixes of matching sides.
-- @usage read(nil, nil, side)
local function side(text, add_space)
expect(1, text, "string")
expect(2, add_space, "boolean", "nil")
return choice_impl(text, sides, add_space)
end
--- Complete a @{settings|setting}.
--
-- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed settings.
-- @treturn { string... } A list of suffixes of matching settings.
-- @usage read(nil, nil, setting)
local function setting(text, add_space)
expect(1, text, "string")
expect(2, add_space, "boolean", "nil")
return choice_impl(text, settings.getNames(), add_space)
end
local command_list
--- Complete the name of a Minecraft @{commands|command}.
--
-- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed command.
-- @treturn { string... } A list of suffixes of matching commands.
-- @usage read(nil, nil, command)
local function command(text, add_space)
expect(1, text, "string")
expect(2, add_space, "boolean", "nil")
if command_list == nil then
command_list = commands and commands.list() or {}
end
return choice_impl(text, command_list, add_space)
end
return {
choice = choice,
peripheral = peripheral_,
side = side,
setting = setting,
command = command,
}

View File

@@ -0,0 +1,151 @@
--- A collection of helper methods for working with shell completion.
--
-- Most programs may be completed using the @{build} helper method, rather than
-- manually switching on the 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,
-- wrap them using @{build}, or your own custom function.
--
-- @module craftos.shell.completion
-- @see cc.completion For more general helpers, suitable for use with @{read}.
-- @see shell.setCompletionFunction
local expect = require "cc.expect".expect
local completion = require "cc.completion"
--- Complete the name of a file relative to the current working directory.
--
-- @tparam shell shell The shell we're completing in
-- @tparam { string... } choices The list of choices to complete from.
-- @treturn { string... } A list of suffixes of matching files.
local function file(shell, text)
return fs.complete(text, shell.dir(), true, false)
end
--- Complete the name of a directory relative to the current working directory.
--
-- @tparam shell shell The shell we're completing in
-- @tparam { string... } choices The list of choices to complete from.
-- @treturn { string... } A list of suffixes of matching directories.
local function dir(shell, text)
return fs.complete(text, shell.dir(), false, true)
end
--- Complete the name of a file or directory relative to the current working
-- directory.
--
-- @tparam shell shell The shell we're completing in
-- @tparam { string... } choices The list of choices to complete from.
-- @tparam { string... } previous The shell arguments before this one.
-- @tparam[opt] boolean add_space Whether to add a space after the completed item.
-- @treturn { string... } A list of suffixes of matching files and directories.
local function dirOrFile(shell, text, previous, add_space)
local results = fs.complete(text, shell.dir(), true, true)
if add_space then
for n = 1, #results do
local result = results[n]
if result:sub(-1) ~= "/" then
results[n] = result .. " "
end
end
end
return results
end
local function wrap(func)
return function(shell, text, previous, ...)
return func(text, ...)
end
end
--- Complete the name of a program.
--
-- @tparam shell shell The shell we're completing in
-- @tparam { string... } choices The list of choices to complete from.
-- @treturn { string... } A list of suffixes of matching programs.
local function program(shell, text)
return shell.completeProgram(text)
end
--- A helper function for building shell completion arguments.
--
-- This accepts a series of single-argument completion functions, and combines
-- them into a function suitable for use with @{shell.setCompletionFunction}.
--
-- @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:
--
-- - `nil`: This argument will not be completed.
--
-- - 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
-- before this one.
--
-- - A table: This acts as a more powerful version of the function case. The table
-- must have a function as the first item - this will be called with the shell,
-- string and preceding arguments as above, but also followed by any additional
-- items in the table. This provides a more convenient interface to pass
-- options to your completion functions.
--
-- If this table is the last argument, it may also set the `many` key to true,
-- which states this function should be used to complete any remaining arguments.
--
-- @usage Prompt for a choice of options, followed by a directory, and then multiple
-- files.
--
-- complete.build(
-- { complete.choice, { "get", "put" } },
-- complete.dir,
-- } complete.file, many = true }
-- )
local function build(...)
local arguments = table.pack(...)
for i = 1, arguments.n do
local arg = arguments[i]
if arg ~= nil then
expect(i, arg, "table", "function")
if type(arg) == "function" then
arg = { arg }
arguments[i] = arg
end
if type(arg[1]) ~= "function" then
error(("Bad table entry #1 at argument #%d (expected function, got %s)"):format(i, type(arg[1])), 2)
end
if arg.many and i < arguments.n then
error(("Unexpected 'many' field on argument #%d (should only occur on the last argument)"):format(i), 2)
end
end
end
return function(shell, index, text, previous)
local arg = arguments[index]
if not arg then
if index <= arguments.n then return end
arg = arguments[arguments.n]
if not arg or not arg.many then return end
end
return arg[1](shell, text, previous, table.unpack(arg, 2))
end
end
return {
file = file,
dir = dir,
dirOrFile = dirOrFile,
program = program,
-- Re-export various other functions
help = wrap(help.completeTopic),
choice = wrap(completion.choice),
peripheral = wrap(completion.peripheral),
side = wrap(completion.side),
setting = wrap(completion.setting),
command = wrap(completion.command),
build = build,
}

View File

@@ -7,7 +7,7 @@ else
end
if sTopic == "index" then
print( "Help topics availiable:" )
print( "Help topics available:" )
local tTopics = help.topics()
textutils.pagedTabulate( tTopics )
return

View File

@@ -1,3 +1,4 @@
local completion = require "cc.shell.completion"
-- Setup paths
local sPath = ".:/rom/programs"
@@ -39,217 +40,86 @@ if term.isColor() then
end
-- Setup completion functions
local function completeMultipleChoice( sText, tOptions, bAddSpaces )
local tResults = {}
for n=1,#tOptions do
local sOption = tOptions[n]
if #sOption + (bAddSpaces and 1 or 0) > #sText and string.sub( sOption, 1, #sText ) == sText then
local sResult = string.sub( sOption, #sText + 1 )
if bAddSpaces then
table.insert( tResults, sResult .. " " )
else
table.insert( tResults, sResult )
end
end
end
return tResults
end
local function completePeripheralName( sText, bAddSpaces )
return completeMultipleChoice( sText, peripheral.getNames(), bAddSpaces )
end
local tRedstoneSides = redstone.getSides()
local function completeSide( sText, bAddSpaces )
return completeMultipleChoice( sText, tRedstoneSides, bAddSpaces )
end
local function completeFile( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return fs.complete( sText, shell.dir(), true, false )
local function completePastebinPut(shell, text, previous)
if previous[2] == "put" then
return fs.complete(text, shell.dir(), true, false )
end
end
local function completeFileMany( shell, nIndex, sText, tPreviousText )
return fs.complete( sText, shell.dir(), true, false )
end
local function completeDir( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return fs.complete( sText, shell.dir(), false, true )
end
end
local function completeEither( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return fs.complete( sText, shell.dir(), true, true )
end
end
local function completeEitherMany( shell, nIndex, sText, tPreviousText )
return fs.complete( sText, shell.dir(), true, true )
end
local function completeEitherEither( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
local tResults = fs.complete( sText, shell.dir(), true, true )
for n=1,#tResults do
local sResult = tResults[n]
if string.sub( sResult, #sResult, #sResult ) ~= "/" then
tResults[n] = sResult .. " "
end
end
return tResults
elseif nIndex == 2 then
return fs.complete( sText, shell.dir(), true, true )
end
end
local function completeProgram( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return shell.completeProgram( sText )
end
end
local function completeHelp( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return help.completeTopic( sText )
end
end
local function completeAlias( shell, nIndex, sText, tPreviousText )
if nIndex == 2 then
return shell.completeProgram( sText )
end
end
local function completePeripheral( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return completePeripheralName( sText )
end
end
local tGPSOptions = { "host", "host ", "locate" }
local function completeGPS( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return completeMultipleChoice( sText, tGPSOptions )
end
end
local tLabelOptions = { "get", "get ", "set ", "clear", "clear " }
local function completeLabel( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return completeMultipleChoice( sText, tLabelOptions )
elseif nIndex == 2 then
return completePeripheralName( sText )
end
end
local function completeMonitor( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return completePeripheralName( sText, true )
elseif nIndex == 2 then
return shell.completeProgram( sText )
end
end
local tRedstoneOptions = { "probe", "set ", "pulse " }
local function completeRedstone( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return completeMultipleChoice( sText, tRedstoneOptions )
elseif nIndex == 2 then
return completeSide( sText )
end
end
local tDJOptions = { "play", "play ", "stop " }
local function completeDJ( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return completeMultipleChoice( sText, tDJOptions )
elseif nIndex == 2 then
return completePeripheralName( sText )
end
end
local tPastebinOptions = { "put ", "get ", "run " }
local function completePastebin( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return completeMultipleChoice( sText, tPastebinOptions )
elseif nIndex == 2 then
if tPreviousText[2] == "put" then
return fs.complete( sText, shell.dir(), true, false )
end
end
end
local tChatOptions = { "host ", "join " }
local function completeChat( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return completeMultipleChoice( sText, tChatOptions )
end
end
local function completeSet( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return completeMultipleChoice( sText, settings.getNames(), true )
end
end
local tCommands
if commands then
tCommands = commands.list()
end
local function completeExec( shell, nIndex, sText, tPreviousText )
if nIndex == 1 and commands then
return completeMultipleChoice( sText, tCommands, true )
end
end
local tWgetOptions = { "run" }
local function completeWget( shell, nIndex, sText, tPreviousText )
if nIndex == 1 then
return completeMultipleChoice( sText, tWgetOptions, true )
end
end
shell.setCompletionFunction( "rom/programs/alias.lua", completeAlias )
shell.setCompletionFunction( "rom/programs/cd.lua", completeDir )
shell.setCompletionFunction( "rom/programs/copy.lua", completeEitherEither )
shell.setCompletionFunction( "rom/programs/delete.lua", completeEitherMany )
shell.setCompletionFunction( "rom/programs/drive.lua", completeDir )
shell.setCompletionFunction( "rom/programs/edit.lua", completeFile )
shell.setCompletionFunction( "rom/programs/eject.lua", completePeripheral )
shell.setCompletionFunction( "rom/programs/gps.lua", completeGPS )
shell.setCompletionFunction( "rom/programs/help.lua", completeHelp )
shell.setCompletionFunction( "rom/programs/id.lua", completePeripheral )
shell.setCompletionFunction( "rom/programs/label.lua", completeLabel )
shell.setCompletionFunction( "rom/programs/list.lua", completeDir )
shell.setCompletionFunction( "rom/programs/mkdir.lua", completeFileMany )
shell.setCompletionFunction( "rom/programs/monitor.lua", completeMonitor )
shell.setCompletionFunction( "rom/programs/move.lua", completeEitherEither )
shell.setCompletionFunction( "rom/programs/redstone.lua", completeRedstone )
shell.setCompletionFunction( "rom/programs/rename.lua", completeEitherEither )
shell.setCompletionFunction( "rom/programs/shell.lua", completeProgram )
shell.setCompletionFunction( "rom/programs/type.lua", completeEither )
shell.setCompletionFunction( "rom/programs/set.lua", completeSet )
shell.setCompletionFunction( "rom/programs/advanced/bg.lua", completeProgram )
shell.setCompletionFunction( "rom/programs/advanced/fg.lua", completeProgram )
shell.setCompletionFunction( "rom/programs/fun/dj.lua", completeDJ )
shell.setCompletionFunction( "rom/programs/fun/advanced/paint.lua", completeFile )
shell.setCompletionFunction( "rom/programs/http/pastebin.lua", completePastebin )
shell.setCompletionFunction( "rom/programs/rednet/chat.lua", completeChat )
shell.setCompletionFunction( "rom/programs/command/exec.lua", completeExec )
shell.setCompletionFunction( "rom/programs/http/wget.lua", completeWget )
shell.setCompletionFunction( "rom/programs/alias.lua", completion.build(nil, completion.program) )
shell.setCompletionFunction( "rom/programs/cd.lua", completion.build(completion.dir) )
shell.setCompletionFunction( "rom/programs/copy.lua", completion.build(
{ completion.dirOrFile, true },
completion.dirOrFile
) )
shell.setCompletionFunction( "rom/programs/delete.lua", completion.build({ completion.dirOrFile, many = true }) )
shell.setCompletionFunction( "rom/programs/drive.lua", completion.build(completion.dir) )
shell.setCompletionFunction( "rom/programs/edit.lua", completion.build(completion.file) )
shell.setCompletionFunction( "rom/programs/eject.lua", completion.build(completion.peripheral) )
shell.setCompletionFunction( "rom/programs/gps.lua", completion.build({ completion.choice, { "host", "host ", "locate" } }) )
shell.setCompletionFunction( "rom/programs/help.lua", completion.build(completion.help) )
shell.setCompletionFunction( "rom/programs/id.lua", completion.build(completion.peripheral) )
shell.setCompletionFunction( "rom/programs/label.lua", completion.build(
{ completion.choice, { "get", "get ", "set ", "clear", "clear " } },
completion.peripheral
) )
shell.setCompletionFunction( "rom/programs/list.lua", completion.build(completion.dir) )
shell.setCompletionFunction( "rom/programs/mkdir.lua", completion.build({ completion.dir, many = true }) )
shell.setCompletionFunction( "rom/programs/monitor.lua", completion.build(
{ completion.peripheral, true },
completion.program
) )
shell.setCompletionFunction( "rom/programs/move.lua", completion.build(
{ completion.dirOrFile, true },
completion.dirOrFile
) )
shell.setCompletionFunction( "rom/programs/redstone.lua", completion.build(
{ completion.choice, { "probe", "set ", "pulse " } },
completion.side
) )
shell.setCompletionFunction( "rom/programs/rename.lua", completion.build(
{ completion.dirOrFile, true },
completion.dirOrFile
) )
shell.setCompletionFunction( "rom/programs/shell.lua", completion.build(completion.program) )
shell.setCompletionFunction( "rom/programs/type.lua", completion.build(completion.dirOrFile) )
shell.setCompletionFunction( "rom/programs/set.lua", completion.build({ completion.setting, true }) )
shell.setCompletionFunction( "rom/programs/advanced/bg.lua", completion.build(completion.program) )
shell.setCompletionFunction( "rom/programs/advanced/fg.lua", completion.build(completion.program) )
shell.setCompletionFunction( "rom/programs/fun/dj.lua", completion.build(
{ completion.choice, { "play", "play ", "stop " } },
completion.peripheral
) )
shell.setCompletionFunction( "rom/programs/fun/advanced/paint.lua", completion.build(completion.file) )
shell.setCompletionFunction( "rom/programs/http/pastebin.lua", completion.build(
{ completion.choice, { "put ", "get ", "run " } },
completePastebinPut
) )
shell.setCompletionFunction( "rom/programs/rednet/chat.lua", completion.build({ completion.choice, { "host ", "join " } }) )
shell.setCompletionFunction( "rom/programs/command/exec.lua", completion.build(completion.command) )
shell.setCompletionFunction( "rom/programs/http/wget.lua", completion.build({ completion.choice, { "run " } }) )
if turtle then
local tGoOptions = { "left", "right", "forward", "back", "down", "up" }
local function completeGo( shell, nIndex, sText )
return completeMultipleChoice( sText, tGoOptions, true)
end
local tTurnOptions = { "left", "right" }
local function completeTurn( shell, nIndex, sText )
return completeMultipleChoice( sText, tTurnOptions, true )
end
local tEquipOptions = { "left", "right" }
local function completeEquip( shell, nIndex, sText )
if nIndex == 2 then
return completeMultipleChoice( sText, tEquipOptions )
end
end
local function completeUnequip( shell, nIndex, sText )
if nIndex == 1 then
return completeMultipleChoice( sText, tEquipOptions )
end
end
shell.setCompletionFunction( "rom/programs/turtle/go.lua", completeGo )
shell.setCompletionFunction( "rom/programs/turtle/turn.lua", completeTurn )
shell.setCompletionFunction( "rom/programs/turtle/equip.lua", completeEquip )
shell.setCompletionFunction( "rom/programs/turtle/unequip.lua", completeUnequip )
shell.setCompletionFunction( "rom/programs/turtle/go.lua", completion.build(
{ completion.choice, { "left", "right", "forward", "back", "down", "up" }, true, many = true }
) )
shell.setCompletionFunction( "rom/programs/turtle/turn.lua", completion.build(
{ completion.choice, { "left", "right" }, true, many = true }
))
shell.setCompletionFunction( "rom/programs/turtle/equip.lua", completion.build(
nil,
{ completion.choice, { "left", "right" } }
) )
shell.setCompletionFunction( "rom/programs/turtle/unequip.lua", completion.build(
{ completion.choice, { "left", "right" } }
) )
end
-- Run autorun files
if fs.exists( "/rom/autorun" ) and fs.isDir( "/rom/autorun" ) then
local tFiles = fs.list( "/rom/autorun" )
table.sort( tFiles )
for n, sFile in ipairs( tFiles ) do
for _, sFile in ipairs( tFiles ) do
if string.sub( sFile, 1, 1 ) ~= "." then
local sPath = "/rom/autorun/"..sFile
if not fs.isDir( sPath ) then

View File

@@ -118,6 +118,26 @@ describe("The window library", function()
expect.error(w.reposition, 1, 1, false, 1):eq("bad argument #3 (expected number, got boolean)")
expect.error(w.reposition, 1, 1, nil, 1):eq("bad argument #3 (expected number, got nil)")
expect.error(w.reposition, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
expect.error(w.reposition, 1, 1, 1, 1, true):eq("bad argument #5 (expected table, got boolean)")
end)
it("can change the buffer", function()
local a, b = mk(), mk()
local target = window.create(a, 1, 1, a.getSize())
target.write("Test")
expect((a.getLine(1))):equal("Test ")
expect({ a.getCursorPos() }):same { 5, 1 }
target.reposition(1, 1, nil, nil, b)
target.redraw()
expect((a.getLine(1))):equal("Test ")
expect({ a.getCursorPos() }):same { 5, 1 }
target.setCursorPos(1, 1) target.write("More")
expect((a.getLine(1))):equal("Test ")
expect((b.getLine(1))):equal("More ")
end)
end)

View File

@@ -0,0 +1,57 @@
describe("cc.completion", function()
local c = require("cc.completion")
describe("choice", function()
it("provides all choices", function()
expect(c.choice("", { "some text", "some other", "other" }))
:same { "some text", "some other", "other" }
end)
it("provides a filtered list of choices", function()
expect(c.choice("som", { "some text", "some other", "other" }))
:same { "e text", "e other" }
expect(c.choice("none", { "some text", "some other", "other" }))
:same { }
end)
it("adds text if needed", function()
expect(c.choice("som", { "some text", "some other", "other" }, true))
:same { "e text ", "e other " }
end)
end)
describe("peripheral", function()
it("provides a choice of peripherals", function()
stub(peripheral, "getNames", function() return { "drive_0", "left" } end)
expect(c.peripheral("dri")):same { "ve_0" }
expect(c.peripheral("dri", true)):same { "ve_0 " }
end)
end)
describe("side", function()
it("provides a choice of sides", function()
expect(c.side("le")):same { "ft" }
expect(c.side("le", true)):same { "ft " }
end)
end)
describe("setting", function()
it("provides a choice of setting names", function()
stub(settings, "getNames", function() return { "shell.allow_startup", "list.show_hidden" } end)
expect(c.setting("li")):same { "st.show_hidden" }
expect(c.setting("li", true)):same { "st.show_hidden " }
end)
end)
describe("command", function()
it("provides a choice of command names", function()
stub(_G, "commands", { list = function() return { "list", "say" } end })
expect(c.command("li")):same { "st" }
expect(c.command("li", true)):same { "st " }
end)
end)
end)

View File

@@ -0,0 +1,41 @@
describe("cc.shell.completion", function()
local c = require "cc.shell.completion"
describe("dirOrFile", function()
it("completes both", function()
expect(c.dirOrFile(shell, "rom/")):same {
"apis/", "apis", "autorun/", "autorun", "help/", "help",
"modules/", "modules", "motd.txt", "programs/", "programs", "startup.lua"
}
end)
it("adds a space", function()
expect(c.dirOrFile(shell, "rom/", nil, true)):same {
"apis/", "apis ", "autorun/", "autorun ", "help/", "help ",
"modules/", "modules ", "motd.txt ", "programs/", "programs ", "startup.lua ",
}
end)
end)
describe("build", function()
it("completes multiple arguments", function()
local spec = c.build(
function() return { "a", "b", "c" } end,
nil,
{ c.choice, { "d", "e", "f"} }
)
expect(spec(shell, 1, "")):same { "a", "b", "c" }
expect(spec(shell, 2, "")):same(nil)
expect(spec(shell, 3, "")):same { "d", "e", "f" }
expect(spec(shell, 4, "")):same(nil)
end)
it("supports variadic completions", function()
local spec = c.build({ function() return { "a", "b", "c" } end, many = true })
expect(spec(shell, 1, "")):same({ "a", "b", "c" })
expect(spec(shell, 2, "")):same({ "a", "b", "c" })
end)
end)
end)

View File

@@ -23,7 +23,7 @@ describe("The mkdir program", function()
io.open("/test-files.a.txt", "w"):close()
local complete = shell.getCompletionInfo()["rom/programs/mkdir.lua"].fnComplete
expect(complete(shell, 1, "/test-files/", {})):same { "a/", "b/" }
expect(complete(shell, 2, "/test-files/", { "/" })):same { "a/", "b/" }
expect(complete(shell, 1, "/test-files/", {})):same { "a/", "a", "b/", "b" }
expect(complete(shell, 2, "/test-files/", { "/" })):same { "a/", "a", "b/", "b" }
end)
end)