Haskell, 330 bytes
import Data.Listmain=interact$unlines.f.linesf(c:a)=(s 4++c):(map(p c.(a%)).tail.inits.t)ct r|r==[]=[]|q x=[x]:t y|0<1=a:t b where(x:y)=r;(a,b)=span(not.q)rq=(`elem`n)p c(a,e)=s(4+(l.init)a)++last a++s(1+length c-l a)++es=(`replicate`'')n="><+-.,[]"e%j=(j,e!!(n#last j))l=length.concat(s:z)#c|[s]==c=0|z==[]=1|0<1=1+z#c
Explanation:
import Data.Listmain=interact$unlines.f.linesf(c:a)=(s 4++c):(map(p c.(a%)).tail.inits.t)c
import Data.List inits
(prefixes), this is used to keep track of the number of spaces
call f
with lines of input, then output each line
f = " " * 4 + code
+map
on each prefix
of each command or noop:
select explanation and then pad with spaces
t r|r==[]=[]|q x=[x]:t y|0<1=a:t b where(x:y)=r;(a,b)=span(not.q)rq=(`elem`n)n="><+-.,[]"
t
splits the code into commands or noopq
checks if a char is a commandn
is the string "><+-.,[]"
p c(a,e)=s(4+(l.init)a)++last a++s(1+length c-l a)++es=(`replicate`'')e%j=(j,e!!(n#last j))(s:z)#c|[s]==c=0|z==[]=1|0<1=1+z#c
p
pads a (prefix, explanation)-pair with spacess
returns n spaces%
creates said (prefix, explanation)-pair#
returns the index of the command in n
or 8 if it's a noop