From aca3c701c07f9ca5fe676468fb1e2184ce7575d3 Mon Sep 17 00:00:00 2001
From: mtfishman <mfishman@flatironinstitute.org>
Date: Tue, 26 Mar 2024 13:43:43 -0400
Subject: [PATCH 1/8] BlockIndices

---
 src/BlockArrays.jl        |  1 +
 src/block_indices.jl      | 18 ++++++++++++++++++
 test/test_blockindices.jl | 31 ++++++++++++++++++++++++++++++-
 3 files changed, 49 insertions(+), 1 deletion(-)
 create mode 100644 src/block_indices.jl

diff --git a/src/BlockArrays.jl b/src/BlockArrays.jl
index c05a66dc..b728a7ba 100644
--- a/src/BlockArrays.jl
+++ b/src/BlockArrays.jl
@@ -56,6 +56,7 @@ end
 include("blockindices.jl")
 include("blockaxis.jl")
 include("abstractblockarray.jl")
+include("block_indices.jl")
 include("blockarray.jl")
 include("pseudo_blockarray.jl")
 include("views.jl")
diff --git a/src/block_indices.jl b/src/block_indices.jl
new file mode 100644
index 00000000..ca030662
--- /dev/null
+++ b/src/block_indices.jl
@@ -0,0 +1,18 @@
+struct BlockIndices{N,R<:NTuple{N,OrdinalRange{Int,Int}}} <: AbstractBlockArray{BlockIndex{N},N}
+  indices::R
+end
+Base.axes(a::BlockIndices) = map(Base.axes1, a.indices)
+BlockIndices(a::AbstractArray) = BlockIndices(axes(a))
+
+function Base.getindex(a::BlockIndices{N}, index::Vararg{Integer,N}) where {N}
+    return BlockIndex(findblockindex.(axes(a), index))
+end
+
+function Base.view(a::BlockIndices{N}, block::Block{N}) where {N}
+    return viewblock(a, block)
+end
+
+function viewblock(a::BlockIndices, block)
+    range = Base.OneTo.(getindex.(blocklengths.(axes(a)), Int.(Tuple(block))))
+    return block[range...]
+end
diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl
index f3beadf2..d2540c6c 100644
--- a/test/test_blockindices.jl
+++ b/test/test_blockindices.jl
@@ -1,6 +1,6 @@
 using BlockArrays, FillArrays, Test, StaticArrays, ArrayLayouts
 using OffsetArrays
-import BlockArrays: BlockIndex, BlockIndexRange, BlockSlice
+import BlockArrays: BlockIndex, BlockIndexRange, BlockIndices, BlockSlice
 
 @testset "Blocks" begin
     @test Int(Block(2)) === Integer(Block(2)) === Number(Block(2)) === 2
@@ -446,3 +446,32 @@ end
     first(eachblock(B))[1,2] = 0
     @test B[1,2] == 0
 end
+
+@testset "BlockIndices" begin
+    v = Array(reshape(1:35, (5, 7)))
+    A = BlockArray(v, [2,3], [3,4])
+    B = BlockIndices(A)
+    @test B == BlockIndices((blockedrange([2,3]), blockedrange([3,4])))
+    @test eltype(B) === BlockIndex{2}
+    @test size(B) == (5, 7)
+    @test axes(B) == (1:5, 1:7)
+    @test blocksize(B) == (2, 2)
+    @test blockaxes(B) == (Block(1):Block(2), Block(1):Block(2))
+    @test B[1, 1] == Block(1, 1)[1, 1]
+    @test B[4, 6] == Block(2, 2)[2, 3]
+
+    @test B[Block(1, 1)] isa BlockArrays.BlockIndexRange{2}
+    @test B[Block(1, 1)] == Block(1, 1)[1:2, 1:3]
+    @test B[Block(2, 1)] == Block(2, 1)[1:3, 1:3]
+    @test B[Block(1, 2)] == Block(1, 2)[1:2, 1:4]
+    @test B[Block(2, 2)] == Block(2, 2)[1:3, 1:4]
+
+    @test view(B, Block(1, 2)) isa BlockArrays.BlockIndexRange{2}
+    @test view(B, Block(1, 2)) == Block(1, 2)[1:2, 1:4]
+
+    @test B[Block(1), Block(2)] isa BlockArrays.BlockIndexRange{2}
+    @test B[Block(1), Block(2)] == Block(1, 2)[1:2, 1:4]
+
+    @test view(B, Block(1), Block(2)) isa BlockArrays.BlockIndexRange{2}
+    @test view(B, Block(1), Block(2)) == Block(1, 2)[1:2, 1:4]
+end

From 010bfed912746a1f8df2438453d1ebee805c8ef9 Mon Sep 17 00:00:00 2001
From: mtfishman <mfishman@flatironinstitute.org>
Date: Tue, 26 Mar 2024 16:43:49 -0400
Subject: [PATCH 2/8] Fix for non-one-based ranges

---
 src/block_indices.jl      | 14 +++++++++++++-
 test/test_blockindices.jl | 22 ++++++++++++++++++----
 2 files changed, 31 insertions(+), 5 deletions(-)

diff --git a/src/block_indices.jl b/src/block_indices.jl
index ca030662..9e28975e 100644
--- a/src/block_indices.jl
+++ b/src/block_indices.jl
@@ -4,14 +4,26 @@ end
 Base.axes(a::BlockIndices) = map(Base.axes1, a.indices)
 BlockIndices(a::AbstractArray) = BlockIndices(axes(a))
 
+function to_blockindex(a::AbstractUnitRange, index::Integer)
+    axis_blockindex = findblockindex(only(axes(a)), index)
+    if !isone(first(a)) && block(axis_blockindex) == Block(1)
+        axis_blockindex = block(axis_blockindex)[blockindex(axis_blockindex) + first(a) - one(eltype(a))]
+    end
+    return axis_blockindex
+end
+
 function Base.getindex(a::BlockIndices{N}, index::Vararg{Integer,N}) where {N}
-    return BlockIndex(findblockindex.(axes(a), index))
+    return BlockIndex(to_blockindex.(a.indices, index))
 end
 
 function Base.view(a::BlockIndices{N}, block::Block{N}) where {N}
     return viewblock(a, block)
 end
 
+function Base.view(a::BlockIndices, block::Vararg{Block{1}})
+  return view(a, Block(block))
+end
+
 function viewblock(a::BlockIndices, block)
     range = Base.OneTo.(getindex.(blocklengths.(axes(a)), Int.(Tuple(block))))
     return block[range...]
diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl
index d2540c6c..2cbdbe3a 100644
--- a/test/test_blockindices.jl
+++ b/test/test_blockindices.jl
@@ -460,18 +460,32 @@ end
     @test B[1, 1] == Block(1, 1)[1, 1]
     @test B[4, 6] == Block(2, 2)[2, 3]
 
-    @test B[Block(1, 1)] isa BlockArrays.BlockIndexRange{2}
+    @test B[Block(1, 1)] isa BlockIndexRange{2}
     @test B[Block(1, 1)] == Block(1, 1)[1:2, 1:3]
     @test B[Block(2, 1)] == Block(2, 1)[1:3, 1:3]
     @test B[Block(1, 2)] == Block(1, 2)[1:2, 1:4]
     @test B[Block(2, 2)] == Block(2, 2)[1:3, 1:4]
 
-    @test view(B, Block(1, 2)) isa BlockArrays.BlockIndexRange{2}
+    @test view(B, Block(1, 2)) isa BlockIndexRange{2}
     @test view(B, Block(1, 2)) == Block(1, 2)[1:2, 1:4]
 
-    @test B[Block(1), Block(2)] isa BlockArrays.BlockIndexRange{2}
+    @test B[Block(1), Block(2)] isa BlockIndexRange{2}
     @test B[Block(1), Block(2)] == Block(1, 2)[1:2, 1:4]
 
-    @test view(B, Block(1), Block(2)) isa BlockArrays.BlockIndexRange{2}
+    @test view(B, Block(1), Block(2)) isa BlockIndexRange{2}
     @test view(B, Block(1), Block(2)) == Block(1, 2)[1:2, 1:4]
+
+    r1 = BlockArrays._BlockedUnitRange(2, [2, 3])
+    r2 = BlockArrays._BlockedUnitRange(2, [3, 4])
+    @test B[2:3, 2:4] == BlockIndices((r1, r2))
+    @test_broken B[2:3, 2:4] isa BlockIndices{2}
+
+    @test B[Block(2, 2)[2:3, 2:3]] == Block(2, 2)[2:3, 2:3]
+    @test_broken B[Block(2, 2)[2:3, 2:3]] isa BlockedIndexRange{2}
+
+    @test B[Block(1):Block(2), Block(1):Block(2)] == B
+    @test_broken B[Block(1):Block(2), Block(1):Block(2)] isa BlockedIndices{2}
+
+    @test B[Block(2), Block(1):Block(2)] == mortar([[Block(2, 1)[1:3, 1:3]] [Block(2, 2)[1:3, 1:4]]])
+    @test_broken B[Block(2), Block(1):Block(2)] isa BlockedIndices{2}
 end

From ea2e171d960ace3332ed1700bec97ab94472cc31 Mon Sep 17 00:00:00 2001
From: mtfishman <mfishman@flatironinstitute.org>
Date: Wed, 27 Mar 2024 11:10:32 -0400
Subject: [PATCH 3/8] Preserve BlockIndices when possible

---
 src/block_indices.jl      | 88 +++++++++++++++++++++++++++++++++++----
 test/test_blockindices.jl | 38 +++++++++++++----
 2 files changed, 109 insertions(+), 17 deletions(-)

diff --git a/src/block_indices.jl b/src/block_indices.jl
index 9e28975e..a10f908c 100644
--- a/src/block_indices.jl
+++ b/src/block_indices.jl
@@ -1,9 +1,3 @@
-struct BlockIndices{N,R<:NTuple{N,OrdinalRange{Int,Int}}} <: AbstractBlockArray{BlockIndex{N},N}
-  indices::R
-end
-Base.axes(a::BlockIndices) = map(Base.axes1, a.indices)
-BlockIndices(a::AbstractArray) = BlockIndices(axes(a))
-
 function to_blockindex(a::AbstractUnitRange, index::Integer)
     axis_blockindex = findblockindex(only(axes(a)), index)
     if !isone(first(a)) && block(axis_blockindex) == Block(1)
@@ -12,19 +6,95 @@ function to_blockindex(a::AbstractUnitRange, index::Integer)
     return axis_blockindex
 end
 
+function blockedunitrange_getindex(a::BlockedUnitRange, indices::AbstractUnitRange)
+  first_block = block(to_blockindex(a, first(indices)))
+  last_block = block(to_blockindex(a, last(indices)))
+  lasts = [blocklasts(a)[Int(first_block):(Int(last_block) - 1)]; last(indices)]
+  return BlockArrays._BlockedUnitRange(first(indices), lasts)
+end
+
+function _BlockIndices end
+
+struct BlockIndices{N,R<:NTuple{N,AbstractUnitRange{Int}}} <: AbstractBlockArray{BlockIndex{N},N}
+  first::NTuple{N,Int}
+  indices::R
+  global function _BlockIndices(first::NTuple{N,Int}, indices::NTuple{N,AbstractUnitRange{Int}}) where {N}
+      return new{N,typeof(indices)}(first, indices)
+  end
+end
+function Base.axes(a::BlockIndices)
+  return map(Base.axes1, blockedunitrange_getindex.(a.indices, range.(a.first, last.(a.indices))))
+end
+function BlockIndices(indices::Tuple{Vararg{AbstractUnitRange{Int}}})
+    first = ntuple(Returns(1), length(indices))
+    return _BlockIndices(first, indices)
+end
+BlockIndices(a::AbstractArray) = BlockIndices(axes(a))
+
 function Base.getindex(a::BlockIndices{N}, index::Vararg{Integer,N}) where {N}
-    return BlockIndex(to_blockindex.(a.indices, index))
+    return BlockIndex(to_blockindex.(a.indices, index .+ a.first .- 1))
 end
 
 function Base.view(a::BlockIndices{N}, block::Block{N}) where {N}
     return viewblock(a, block)
 end
 
-function Base.view(a::BlockIndices, block::Vararg{Block{1}})
-  return view(a, Block(block))
+function Base.view(a::BlockIndices{1}, block::Block{1})
+    return viewblock(a, block)
+end
+
+function Base.view(a::BlockIndices{N}, block::Vararg{Block{1},N}) where {N}
+    return view(a, Block(block))
 end
 
 function viewblock(a::BlockIndices, block)
     range = Base.OneTo.(getindex.(blocklengths.(axes(a)), Int.(Tuple(block))))
     return block[range...]
 end
+
+function Base.view(a::BlockIndices{N}, indices::Vararg{BlockIndexRange{1},N}) where {N}
+    return view(a, BlockIndexRange(Block(block.(indices)), only.(getfield.(indices, :indices))))
+end
+
+function Base.view(a::BlockIndices{N}, indices::BlockIndexRange{N}) where {N}
+    a_block = a[block(indices)]
+    return block(a_block)[getindex.(a_block.indices, indices.indices)...]
+end
+
+# Circumvent that this is getting hijacked to call `a[block(indices)][indices.indices...]`,
+# which hits the bug https://github.com/JuliaArrays/BlockArrays.jl/issues/355.
+function Base.getindex(a::BlockIndices{N}, indices::BlockIndexRange{N}) where {N}
+    return view(a, indices)
+end
+
+function Base.view(a::BlockIndices{N}, indices::Vararg{AbstractUnitRange,N}) where {N}
+    offsets = a.first .- ntuple(Returns(1), Val(N))
+    firsts = first.(indices) .+ offsets
+    inds = blockedunitrange_getindex.(a.indices, Base.OneTo.(last.(indices) .+ offsets))
+    return _BlockIndices(firsts, inds)
+end
+
+# Ranges that result in contiguous slices, and therefore preserve `BlockIndices`.
+const BlockOrUnitRanges = Union{AbstractUnitRange,CartesianIndices{1},Block{1},BlockRange{1},BlockIndexRange{1}}
+
+function Base.view(a::BlockIndices{N}, indices::Vararg{BlockOrUnitRanges,N}) where {N}
+    return view(a, to_indices(a, indices)...)
+end
+
+function Base.view(a::BlockIndices{N}, indices::CartesianIndices{N}) where {N}
+    return view(a, to_indices(a, (indices,))...)
+end
+
+# For some reason this doesn't call `view` automatically.
+function Base.getindex(a::BlockIndices{N}, indices::CartesianIndices{N}) where {N}
+    return view(a, to_indices(a, (indices,))...)
+end
+
+function Base.view(a::BlockIndices{N}, indices::BlockRange{N}) where {N}
+    return view(a, to_indices(a, (indices,))...)
+end
+
+# For some reason this doesn't call `view` automatically.
+function Base.getindex(a::BlockIndices{N}, indices::BlockRange{N}) where {N}
+    return view(a, to_indices(a, (indices,))...)
+end
diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl
index 2cbdbe3a..6a7523b0 100644
--- a/test/test_blockindices.jl
+++ b/test/test_blockindices.jl
@@ -449,12 +449,13 @@ end
 
 @testset "BlockIndices" begin
     v = Array(reshape(1:35, (5, 7)))
-    A = BlockArray(v, [2,3], [3,4])
+    A = BlockArray(v, [2, 3], [3, 4])
     B = BlockIndices(A)
     @test B == BlockIndices((blockedrange([2,3]), blockedrange([3,4])))
     @test eltype(B) === BlockIndex{2}
     @test size(B) == (5, 7)
     @test axes(B) == (1:5, 1:7)
+    @test blocklengths.(axes(B)) == ([2, 3], [3, 4])
     @test blocksize(B) == (2, 2)
     @test blockaxes(B) == (Block(1):Block(2), Block(1):Block(2))
     @test B[1, 1] == Block(1, 1)[1, 1]
@@ -475,17 +476,38 @@ end
     @test view(B, Block(1), Block(2)) isa BlockIndexRange{2}
     @test view(B, Block(1), Block(2)) == Block(1, 2)[1:2, 1:4]
 
-    r1 = BlockArrays._BlockedUnitRange(2, [2, 3])
-    r2 = BlockArrays._BlockedUnitRange(2, [3, 4])
-    @test B[2:3, 2:4] == BlockIndices((r1, r2))
-    @test_broken B[2:3, 2:4] isa BlockIndices{2}
+    B23_24 = mortar([[Block(1, 1)[2:2, 2:3]] [Block(1, 2)[2:2, 1:1]]
+                     [Block(2, 1)[1:1, 2:3]] [Block(2, 2)[1:1, 1:1]]])
+    @test B[2:3, 2:4] == B23_24
+    @test B[2:3, 2:4] isa BlockIndices{2}
+    @test B[CartesianIndices((2:3,)), CartesianIndices((2:4,))] == B23_24
+    @test B[CartesianIndices((2:3,)), CartesianIndices((2:4,))] isa BlockIndices{2}
+    @test B[CartesianIndices((2:3, 2:4))] == B23_24
+    @test B[CartesianIndices((2:3, 2:4))] isa BlockIndices{2}
 
     @test B[Block(2, 2)[2:3, 2:3]] == Block(2, 2)[2:3, 2:3]
-    @test_broken B[Block(2, 2)[2:3, 2:3]] isa BlockedIndexRange{2}
+    @test B[Block(2, 2)[2:3, 2:3]] isa BlockIndexRange{2}
 
     @test B[Block(1):Block(2), Block(1):Block(2)] == B
-    @test_broken B[Block(1):Block(2), Block(1):Block(2)] isa BlockedIndices{2}
+    @test B[Block(1):Block(2), Block(1):Block(2)] isa BlockIndices{2}
+
+    @test B[BlockRange(1:2, 1:2)] == B
+    @test B[BlockRange(1:2, 1:2)] isa BlockIndices{2}
 
     @test B[Block(2), Block(1):Block(2)] == mortar([[Block(2, 1)[1:3, 1:3]] [Block(2, 2)[1:3, 1:4]]])
-    @test_broken B[Block(2), Block(1):Block(2)] isa BlockedIndices{2}
+    @test B[Block(2), Block(1):Block(2)] isa BlockIndices{2}
+
+    @test B[2:4, 2:5][2:3, 2:3] == mortar([[Block(2, 1)[1:2, 3:3]] [Block(2, 2)[1:2, 1:1]]])
+    @test B[2:4, 2:5][2:3, 2:3] isa BlockIndices{2}
+
+    B = BlockIndices(blockedrange([2, 3]))
+    @test B == [Block(1)[1], Block(1)[2], Block(2)[1], Block(2)[2], Block(2)[3]]
+    @test blocklengths(only(axes(B))) == [2, 3]
+    @test B[Block(1)] == Block(1)[1:2]
+    @test B[Block(1)] isa BlockIndexRange{1}
+    @test B[Block(2)] == Block(2)[1:3]
+    @test B[Block(2)] isa BlockIndexRange{1}
+    @test B[2:4] == [Block(1)[2], Block(2)[1], Block(2)[2]]
+    @test blocklengths(only(axes(B[2:4]))) == [1, 2]
+    @test B[2:4] isa BlockIndices{1}
 end

From 027130e3a226c16b755b729bb210839c7ab7a6ce Mon Sep 17 00:00:00 2001
From: mtfishman <mfishman@flatironinstitute.org>
Date: Wed, 27 Mar 2024 12:00:11 -0400
Subject: [PATCH 4/8] Julia 1.6 compatibility

---
 src/block_indices.jl | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/block_indices.jl b/src/block_indices.jl
index a10f908c..99c07552 100644
--- a/src/block_indices.jl
+++ b/src/block_indices.jl
@@ -25,8 +25,8 @@ end
 function Base.axes(a::BlockIndices)
   return map(Base.axes1, blockedunitrange_getindex.(a.indices, range.(a.first, last.(a.indices))))
 end
-function BlockIndices(indices::Tuple{Vararg{AbstractUnitRange{Int}}})
-    first = ntuple(Returns(1), length(indices))
+function BlockIndices(indices::Tuple{Vararg{AbstractUnitRange{Int},N}}) where {N}
+    first = ntuple(_ -> 1, Val(N))
     return _BlockIndices(first, indices)
 end
 BlockIndices(a::AbstractArray) = BlockIndices(axes(a))
@@ -68,7 +68,7 @@ function Base.getindex(a::BlockIndices{N}, indices::BlockIndexRange{N}) where {N
 end
 
 function Base.view(a::BlockIndices{N}, indices::Vararg{AbstractUnitRange,N}) where {N}
-    offsets = a.first .- ntuple(Returns(1), Val(N))
+    offsets = a.first .- ntuple(_ -> 1, Val(N))
     firsts = first.(indices) .+ offsets
     inds = blockedunitrange_getindex.(a.indices, Base.OneTo.(last.(indices) .+ offsets))
     return _BlockIndices(firsts, inds)

From d17f762f1cee8033c7302f186455bddc35112beb Mon Sep 17 00:00:00 2001
From: mtfishman <mfishman@flatironinstitute.org>
Date: Wed, 27 Mar 2024 12:14:51 -0400
Subject: [PATCH 5/8] Another fix for Julia 1.6

---
 src/block_indices.jl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/block_indices.jl b/src/block_indices.jl
index 99c07552..1ce351ef 100644
--- a/src/block_indices.jl
+++ b/src/block_indices.jl
@@ -23,7 +23,7 @@ struct BlockIndices{N,R<:NTuple{N,AbstractUnitRange{Int}}} <: AbstractBlockArray
   end
 end
 function Base.axes(a::BlockIndices)
-  return map(Base.axes1, blockedunitrange_getindex.(a.indices, range.(a.first, last.(a.indices))))
+  return map(Base.axes1, blockedunitrange_getindex.(a.indices, (:).(a.first, last.(a.indices))))
 end
 function BlockIndices(indices::Tuple{Vararg{AbstractUnitRange{Int},N}}) where {N}
     first = ntuple(_ -> 1, Val(N))

From 328cce99c9778a991a168c37d81ff795b236669e Mon Sep 17 00:00:00 2001
From: mtfishman <mfishman@flatironinstitute.org>
Date: Thu, 28 Mar 2024 17:57:11 -0400
Subject: [PATCH 6/8] Axes of slices match axes of input indices

---
 src/block_indices.jl      | 24 ++++++++++++++----------
 test/test_blockindices.jl | 35 +++++++++++++++++++++++------------
 2 files changed, 37 insertions(+), 22 deletions(-)

diff --git a/src/block_indices.jl b/src/block_indices.jl
index 1ce351ef..a2ad0b61 100644
--- a/src/block_indices.jl
+++ b/src/block_indices.jl
@@ -6,28 +6,32 @@ function to_blockindex(a::AbstractUnitRange, index::Integer)
     return axis_blockindex
 end
 
-function blockedunitrange_getindex(a::BlockedUnitRange, indices::AbstractUnitRange)
+# https://github.com/JuliaArrays/BlockArrays.jl/issues/347
+function blockedgetindex(a::BlockedUnitRange, indices::AbstractUnitRange)
   first_block = block(to_blockindex(a, first(indices)))
   last_block = block(to_blockindex(a, last(indices)))
   lasts = [blocklasts(a)[Int(first_block):(Int(last_block) - 1)]; last(indices)]
   return BlockArrays._BlockedUnitRange(first(indices), lasts)
 end
+function blockedgetindex(a::AbstractUnitRange, indices::AbstractUnitRange)
+  return a[indices]
+end
 
 function _BlockIndices end
 
-struct BlockIndices{N,R<:NTuple{N,AbstractUnitRange{Int}}} <: AbstractBlockArray{BlockIndex{N},N}
+struct BlockIndices{N,R<:NTuple{N,AbstractUnitRange{Int}},BS<:NTuple{N,AbstractUnitRange{Int}}} <: AbstractBlockArray{BlockIndex{N},N}
   first::NTuple{N,Int}
   indices::R
-  global function _BlockIndices(first::NTuple{N,Int}, indices::NTuple{N,AbstractUnitRange{Int}}) where {N}
-      return new{N,typeof(indices)}(first, indices)
+  axes::BS
+  global function _BlockIndices(first::NTuple{N,Int}, indices::R, axes::BS) where {N,R<:NTuple{N,AbstractUnitRange{Int}},BS<:NTuple{N,AbstractUnitRange{Int}}}
+      return new{N,R,BS}(first, indices, axes)
   end
 end
-function Base.axes(a::BlockIndices)
-  return map(Base.axes1, blockedunitrange_getindex.(a.indices, (:).(a.first, last.(a.indices))))
-end
+Base.axes(a::BlockIndices) = a.axes
 function BlockIndices(indices::Tuple{Vararg{AbstractUnitRange{Int},N}}) where {N}
     first = ntuple(_ -> 1, Val(N))
-    return _BlockIndices(first, indices)
+    axes = map(Base.axes1, blockedgetindex.(indices, Base.OneTo.(last.(indices))))
+    return _BlockIndices(first, indices, axes)
 end
 BlockIndices(a::AbstractArray) = BlockIndices(axes(a))
 
@@ -70,8 +74,8 @@ end
 function Base.view(a::BlockIndices{N}, indices::Vararg{AbstractUnitRange,N}) where {N}
     offsets = a.first .- ntuple(_ -> 1, Val(N))
     firsts = first.(indices) .+ offsets
-    inds = blockedunitrange_getindex.(a.indices, Base.OneTo.(last.(indices) .+ offsets))
-    return _BlockIndices(firsts, inds)
+    inds = blockedgetindex.(a.indices, Base.OneTo.(last.(indices) .+ offsets))
+    return _BlockIndices(firsts, inds, Base.axes1.(indices))
 end
 
 # Ranges that result in contiguous slices, and therefore preserve `BlockIndices`.
diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl
index 6a7523b0..ce6d955e 100644
--- a/test/test_blockindices.jl
+++ b/test/test_blockindices.jl
@@ -457,7 +457,7 @@ end
     @test axes(B) == (1:5, 1:7)
     @test blocklengths.(axes(B)) == ([2, 3], [3, 4])
     @test blocksize(B) == (2, 2)
-    @test blockaxes(B) == (Block(1):Block(2), Block(1):Block(2))
+    @test blockaxes(B) == (Block.(1:2), Block.(1:2))
     @test B[1, 1] == Block(1, 1)[1, 1]
     @test B[4, 6] == Block(2, 2)[2, 3]
 
@@ -478,24 +478,33 @@ end
 
     B23_24 = mortar([[Block(1, 1)[2:2, 2:3]] [Block(1, 2)[2:2, 1:1]]
                      [Block(2, 1)[1:1, 2:3]] [Block(2, 2)[1:1, 1:1]]])
-    @test B[2:3, 2:4] == B23_24
-    @test B[2:3, 2:4] isa BlockIndices{2}
-    @test B[CartesianIndices((2:3,)), CartesianIndices((2:4,))] == B23_24
-    @test B[CartesianIndices((2:3,)), CartesianIndices((2:4,))] isa BlockIndices{2}
-    @test B[CartesianIndices((2:3, 2:4))] == B23_24
-    @test B[CartesianIndices((2:3, 2:4))] isa BlockIndices{2}
+    Br = B[2:3, 2:4]
+    @test Br == B23_24
+    @test blocksize(Br) == (1, 1)
+    @test Br isa AbstractMatrix{<:BlockIndex{2}}
+    @test Br isa BlockIndices{2}
+    Br = B[CartesianIndices((2:3,)), CartesianIndices((2:4,))]
+    @test Br == B23_24
+    @test blocksize(Br) == (1, 1)
+    @test Br isa AbstractMatrix{<:BlockIndex{2}}
+    @test Br isa BlockIndices{2}
+    Br = B[CartesianIndices((2:3, 2:4))]
+    @test Br == B23_24
+    @test Br isa AbstractMatrix{<:BlockIndex{2}}
+    @test Br isa BlockIndices{2}
 
     @test B[Block(2, 2)[2:3, 2:3]] == Block(2, 2)[2:3, 2:3]
     @test B[Block(2, 2)[2:3, 2:3]] isa BlockIndexRange{2}
 
-    @test B[Block(1):Block(2), Block(1):Block(2)] == B
-    @test B[Block(1):Block(2), Block(1):Block(2)] isa BlockIndices{2}
+    @test B[Block.(1:2), Block.(1:2)] == B
+    @test B[Block.(1:2), Block.(1:2)] isa BlockIndices{2}
 
     @test B[BlockRange(1:2, 1:2)] == B
     @test B[BlockRange(1:2, 1:2)] isa BlockIndices{2}
 
-    @test B[Block(2), Block(1):Block(2)] == mortar([[Block(2, 1)[1:3, 1:3]] [Block(2, 2)[1:3, 1:4]]])
-    @test B[Block(2), Block(1):Block(2)] isa BlockIndices{2}
+    # TODO: Should this make a `BlockIndices{1}`?
+    @test B[Block(2), Block.(1:2)] == mortar([[Block(2, 1)[1:3, 1:3]] [Block(2, 2)[1:3, 1:4]]])
+    @test B[Block(2), Block.(1:2)] isa BlockIndices{2}
 
     @test B[2:4, 2:5][2:3, 2:3] == mortar([[Block(2, 1)[1:2, 3:3]] [Block(2, 2)[1:2, 1:1]]])
     @test B[2:4, 2:5][2:3, 2:3] isa BlockIndices{2}
@@ -508,6 +517,8 @@ end
     @test B[Block(2)] == Block(2)[1:3]
     @test B[Block(2)] isa BlockIndexRange{1}
     @test B[2:4] == [Block(1)[2], Block(2)[1], Block(2)[2]]
-    @test blocklengths(only(axes(B[2:4]))) == [1, 2]
+    @test blocklength(only(axes(B[2:4]))) == 1
+    @test blocklengths(only(axes(B[2:4]))) == [3]
+    @test B[2:4] isa AbstractVector{<:BlockIndex{1}}
     @test B[2:4] isa BlockIndices{1}
 end

From fbe6c0bd3384e9bda19c7a64dc79ffbfc56782e0 Mon Sep 17 00:00:00 2001
From: mtfishman <mfishman@flatironinstitute.org>
Date: Thu, 28 Mar 2024 18:09:09 -0400
Subject: [PATCH 7/8] Fix test

---
 test/test_blockindices.jl | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl
index ce6d955e..d1ca5481 100644
--- a/test/test_blockindices.jl
+++ b/test/test_blockindices.jl
@@ -502,9 +502,8 @@ end
     @test B[BlockRange(1:2, 1:2)] == B
     @test B[BlockRange(1:2, 1:2)] isa BlockIndices{2}
 
-    # TODO: Should this make a `BlockIndices{1}`?
-    @test B[Block(2), Block.(1:2)] == mortar([[Block(2, 1)[1:3, 1:3]] [Block(2, 2)[1:3, 1:4]]])
-    @test B[Block(2), Block.(1:2)] isa BlockIndices{2}
+    @test B[Block.(2:2), Block.(1:2)] == mortar([[Block(2, 1)[1:3, 1:3]] [Block(2, 2)[1:3, 1:4]]])
+    @test B[Block.(2:2), Block.(1:2)] isa BlockIndices{2}
 
     @test B[2:4, 2:5][2:3, 2:3] == mortar([[Block(2, 1)[1:2, 3:3]] [Block(2, 2)[1:2, 1:1]]])
     @test B[2:4, 2:5][2:3, 2:3] isa BlockIndices{2}

From 43b459bddb5a9ea6d77e2f507e33f14d999523ce Mon Sep 17 00:00:00 2001
From: mtfishman <mfishman@flatironinstitute.org>
Date: Fri, 29 Mar 2024 19:12:06 -0400
Subject: [PATCH 8/8] Add support for indexing with BlockIndices

---
 src/block_indices.jl      | 12 ++++++++++++
 test/test_blockindices.jl | 21 +++++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/src/block_indices.jl b/src/block_indices.jl
index a2ad0b61..813b7650 100644
--- a/src/block_indices.jl
+++ b/src/block_indices.jl
@@ -102,3 +102,15 @@ end
 function Base.getindex(a::BlockIndices{N}, indices::BlockRange{N}) where {N}
     return view(a, to_indices(a, (indices,))...)
 end
+
+function Base.getindex(a::CartesianIndices{N}, indices::BlockIndices{N}) where {N}
+  # TODO: Is there a better way to write this?
+  new_axes = (:).(Tuple(a[first(indices)]), Tuple(a[last(indices)]))
+  firsts = first.(new_axes)
+  blocklasts = ntuple(i -> accumulate(+, blocklengths(axes(indices, i)); init=firsts[i] - one(firsts[i])), Val(N))
+  return CartesianIndices(_BlockedUnitRange.(firsts, blocklasts))
+end
+
+function Base.getindex(a::AbstractArray{<:Any,N}, indices::BlockIndices{N}) where {N}
+  return a[CartesianIndices(a)[indices]]
+end
diff --git a/test/test_blockindices.jl b/test/test_blockindices.jl
index d1ca5481..8bdc7088 100644
--- a/test/test_blockindices.jl
+++ b/test/test_blockindices.jl
@@ -520,4 +520,25 @@ end
     @test blocklengths(only(axes(B[2:4]))) == [3]
     @test B[2:4] isa AbstractVector{<:BlockIndex{1}}
     @test B[2:4] isa BlockIndices{1}
+
+    A = BlockArray(randn(5, 5), [2, 3], [2, 3])
+    BA = BlockIndices(A)
+    r = BlockArrays._BlockedUnitRange(2, [2, 4])
+    B = BA[r, r]
+    @test B == BA[2:4, 2:4]
+    @test size(B) == (3, 3)
+    @test blocksize(B) == (2, 2)
+    @test blocklengths.(axes(B)) == ([1, 2], [1, 2])
+    CB = CartesianIndices(A)[B]
+    @test CB == CartesianIndices((2:4, 2:4))
+    @test size(CB) == (3, 3)
+    @test blocksize(CB) == (2, 2)
+    @test blocklengths.(axes(CB)) == ([1, 2], [1, 2])
+    @test all(blockisequal.(axes(CB), axes(B)))
+    AB = A[B]
+    @test AB == A[2:4, 2:4]
+    @test size(AB) == (3, 3)
+    @test blocksize(AB) == (2, 2)
+    @test blocklengths.(axes(AB)) == ([1, 2], [1, 2])
+    @test all(blockisequal.(axes(AB), axes(B)))
 end