mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-11-27 23:11:20 +08:00
Merge branch 'num-workers' into issue-648-peach-num-worker
This commit is contained in:
commit
1f0d1c8251
|
@ -5,6 +5,11 @@ Draft release notes for Elvish 0.20.0.
|
|||
- The `peach` command now has a `&num-workers` option
|
||||
([#648](https://github.com/elves/elvish/issues/648)).
|
||||
|
||||
- A new `str:fields` command ([#1689](https://b.elv.sh/1689)).
|
||||
|
||||
- The language server now supports showing the documentation of builtin
|
||||
functions and variables on hover ([#1684](https://b.elv.sh/1684)).
|
||||
|
||||
# Breaking changes
|
||||
|
||||
- The `except` keyword in the `try` command was deprecated since 0.18.0 and is
|
||||
|
|
3
go.mod
3
go.mod
|
@ -4,11 +4,12 @@ require (
|
|||
github.com/creack/pty v1.1.15
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/mattn/go-isatty v0.0.17
|
||||
github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d
|
||||
github.com/sourcegraph/jsonrpc2 v0.2.0
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.5.0
|
||||
)
|
||||
|
||||
require pkg.nimblebun.works/go-lsp v1.1.0
|
||||
|
||||
go 1.19
|
||||
|
|
4
go.sum
4
go.sum
|
@ -8,8 +8,6 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
|||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d h1:afLbh+ltiygTOB37ymZVwKlJwWZn+86syPTbrrOAydY=
|
||||
github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d/go.mod h1:SULmZY7YNBsvNiQbrb/BEDdEJ84TGnfyUQxaHt8t8rY=
|
||||
github.com/sourcegraph/jsonrpc2 v0.2.0 h1:KjN/dC4fP6aN9030MZCJs9WQbTOjWHhrtKVpzzSrr/U=
|
||||
github.com/sourcegraph/jsonrpc2 v0.2.0/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
|
@ -21,3 +19,5 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
pkg.nimblebun.works/go-lsp v1.1.0 h1:TH5ro4p2vlDtELK4LoVeKs4TsKm6aW1f5WP8jHm/9m4=
|
||||
pkg.nimblebun.works/go-lsp v1.1.0/go.mod h1:Suh759Ki+DjU0zwf0xkl1H6Ln1C6/+GtYyNofbtfcug=
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"src.elv.sh/pkg/diag"
|
||||
"src.elv.sh/pkg/eval"
|
||||
"src.elv.sh/pkg/parse"
|
||||
"src.elv.sh/pkg/parse/np"
|
||||
)
|
||||
|
||||
// An error returned by Complete as well as the completers if there is no
|
||||
|
@ -64,7 +65,7 @@ func Complete(code CodeBuffer, ev *eval.Evaler, cfg Config) (*Result, error) {
|
|||
|
||||
// Ignore the error; the function always returns a valid *ChunkNode.
|
||||
tree, _ := parse.Parse(parse.Source{Name: "[interactive]", Code: code.Content}, parse.Config{})
|
||||
path := findNodePath(tree.Root, code.Dot)
|
||||
path := np.FindLeft(tree.Root, code.Dot)
|
||||
if len(path) == 0 {
|
||||
// This can happen when there is a parse error.
|
||||
return nil, errNoCompletion
|
||||
|
|
|
@ -4,9 +4,10 @@ import (
|
|||
"src.elv.sh/pkg/diag"
|
||||
"src.elv.sh/pkg/eval"
|
||||
"src.elv.sh/pkg/parse"
|
||||
"src.elv.sh/pkg/parse/np"
|
||||
)
|
||||
|
||||
var completers = []func(nodePath, *eval.Evaler, Config) (*context, []RawItem, error){
|
||||
var completers = []func(np.Path, *eval.Evaler, Config) (*context, []RawItem, error){
|
||||
completeCommand,
|
||||
completeIndex,
|
||||
completeRedir,
|
||||
|
@ -21,63 +22,63 @@ type context struct {
|
|||
interval diag.Ranging
|
||||
}
|
||||
|
||||
func completeArg(np nodePath, ev *eval.Evaler, cfg Config) (*context, []RawItem, error) {
|
||||
func completeArg(p np.Path, ev *eval.Evaler, cfg Config) (*context, []RawItem, error) {
|
||||
var form *parse.Form
|
||||
if np.match(aSep, store(&form)) && form.Head != nil {
|
||||
if p.Match(np.Sep, np.Store(&form)) && form.Head != nil {
|
||||
// Case 1: starting a new argument.
|
||||
ctx := &context{"argument", "", parse.Bareword, range0(np[0].Range().To)}
|
||||
args := purelyEvalForm(form, "", np[0].Range().To, ev)
|
||||
items, err := generateArgs(args, ev, np, cfg)
|
||||
ctx := &context{"argument", "", parse.Bareword, range0(p[0].Range().To)}
|
||||
args := purelyEvalForm(form, "", p[0].Range().To, ev)
|
||||
items, err := generateArgs(args, ev, p, cfg)
|
||||
return ctx, items, err
|
||||
}
|
||||
|
||||
expr := simpleExpr(ev)
|
||||
if np.match(expr, store(&form)) && form.Head != nil && form.Head != expr.compound {
|
||||
var expr np.SimpleExprData
|
||||
if p.Match(np.SimpleExpr(&expr, ev), np.Store(&form)) && form.Head != nil && form.Head != expr.Compound {
|
||||
// Case 2: in an incomplete argument.
|
||||
ctx := &context{"argument", expr.s, expr.quote, expr.compound.Range()}
|
||||
args := purelyEvalForm(form, expr.s, expr.compound.Range().From, ev)
|
||||
items, err := generateArgs(args, ev, np, cfg)
|
||||
ctx := &context{"argument", expr.Value, expr.PrimarType, expr.Compound.Range()}
|
||||
args := purelyEvalForm(form, expr.Value, expr.Compound.Range().From, ev)
|
||||
items, err := generateArgs(args, ev, p, cfg)
|
||||
return ctx, items, err
|
||||
}
|
||||
|
||||
return nil, nil, errNoCompletion
|
||||
}
|
||||
|
||||
func completeCommand(np nodePath, ev *eval.Evaler, cfg Config) (*context, []RawItem, error) {
|
||||
func completeCommand(p np.Path, ev *eval.Evaler, cfg Config) (*context, []RawItem, error) {
|
||||
generateForEmpty := func(pos int) (*context, []RawItem, error) {
|
||||
ctx := &context{"command", "", parse.Bareword, range0(pos)}
|
||||
items, err := generateCommands("", ev, np)
|
||||
items, err := generateCommands("", ev, p)
|
||||
return ctx, items, err
|
||||
}
|
||||
|
||||
if np.match(aChunk) {
|
||||
if p.Match(np.Chunk) {
|
||||
// Case 1: The leaf is a Chunk. That means that the chunk is empty
|
||||
// (nothing entered at all) and it is a correct place for completing a
|
||||
// command.
|
||||
return generateForEmpty(np[0].Range().To)
|
||||
return generateForEmpty(p[0].Range().To)
|
||||
}
|
||||
if np.match(aSep, aChunk) || np.match(aSep, aPipeline) {
|
||||
if p.Match(np.Sep, np.Chunk) || p.Match(np.Sep, np.Pipeline) {
|
||||
// Case 2: Just after a newline, semicolon, or a pipe.
|
||||
return generateForEmpty(np[0].Range().To)
|
||||
return generateForEmpty(p[0].Range().To)
|
||||
}
|
||||
|
||||
var primary *parse.Primary
|
||||
if np.match(aSep, store(&primary)) {
|
||||
if p.Match(np.Sep, np.Store(&primary)) {
|
||||
t := primary.Type
|
||||
if t == parse.OutputCapture || t == parse.ExceptionCapture || t == parse.Lambda {
|
||||
// Case 3: At the beginning of output, exception capture or lambda.
|
||||
//
|
||||
// TODO: Don't trigger after "{|".
|
||||
return generateForEmpty(np[0].Range().To)
|
||||
return generateForEmpty(p[0].Range().To)
|
||||
}
|
||||
}
|
||||
|
||||
expr := simpleExpr(ev)
|
||||
var expr np.SimpleExprData
|
||||
var form *parse.Form
|
||||
if np.match(expr, store(&form)) && form.Head == expr.compound {
|
||||
if p.Match(np.SimpleExpr(&expr, ev), np.Store(&form)) && form.Head == expr.Compound {
|
||||
// Case 4: At an already started command.
|
||||
ctx := &context{"command", expr.s, expr.quote, expr.compound.Range()}
|
||||
items, err := generateCommands(expr.s, ev, np)
|
||||
ctx := &context{"command", expr.Value, expr.PrimarType, expr.Compound.Range()}
|
||||
items, err := generateCommands(expr.Value, ev, p)
|
||||
return ctx, items, err
|
||||
}
|
||||
|
||||
|
@ -86,30 +87,30 @@ func completeCommand(np nodePath, ev *eval.Evaler, cfg Config) (*context, []RawI
|
|||
|
||||
// NOTE: This now only supports a single level of indexing; for instance,
|
||||
// $a[<Tab> is supported, but $a[x][<Tab> is not.
|
||||
func completeIndex(np nodePath, ev *eval.Evaler, cfg Config) (*context, []RawItem, error) {
|
||||
func completeIndex(p np.Path, ev *eval.Evaler, cfg Config) (*context, []RawItem, error) {
|
||||
generateForEmpty := func(v any, pos int) (*context, []RawItem, error) {
|
||||
ctx := &context{"index", "", parse.Bareword, range0(pos)}
|
||||
return ctx, generateIndices(v), nil
|
||||
}
|
||||
|
||||
var indexing *parse.Indexing
|
||||
if np.match(aSep, store(&indexing)) || np.match(aSep, aArray, store(&indexing)) {
|
||||
if p.Match(np.Sep, np.Store(&indexing)) || p.Match(np.Sep, np.Array, np.Store(&indexing)) {
|
||||
// We are at a new index, either directly after the opening bracket, or
|
||||
// after an existing index and some spaces.
|
||||
if len(indexing.Indices) == 1 {
|
||||
if indexee := ev.PurelyEvalPrimary(indexing.Head); indexee != nil {
|
||||
return generateForEmpty(indexee, np[0].Range().To)
|
||||
return generateForEmpty(indexee, p[0].Range().To)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expr := simpleExpr(ev)
|
||||
if np.match(expr, aArray, store(&indexing)) {
|
||||
var expr np.SimpleExprData
|
||||
if p.Match(np.SimpleExpr(&expr, ev), np.Array, np.Store(&indexing)) {
|
||||
// We are just after an incomplete index.
|
||||
if len(indexing.Indices) == 1 {
|
||||
if indexee := ev.PurelyEvalPrimary(indexing.Head); indexee != nil {
|
||||
ctx := &context{
|
||||
"index", expr.s, expr.quote, expr.compound.Range()}
|
||||
"index", expr.Value, expr.PrimarType, expr.Compound.Range()}
|
||||
return ctx, generateIndices(indexee), nil
|
||||
}
|
||||
}
|
||||
|
@ -118,27 +119,27 @@ func completeIndex(np nodePath, ev *eval.Evaler, cfg Config) (*context, []RawIte
|
|||
return nil, nil, errNoCompletion
|
||||
}
|
||||
|
||||
func completeRedir(np nodePath, ev *eval.Evaler, cfg Config) (*context, []RawItem, error) {
|
||||
if np.match(aSep, aRedir) {
|
||||
func completeRedir(p np.Path, ev *eval.Evaler, cfg Config) (*context, []RawItem, error) {
|
||||
if p.Match(np.Sep, np.Redir) {
|
||||
// Empty redirection target.
|
||||
ctx := &context{"redir", "", parse.Bareword, range0(np[0].Range().To)}
|
||||
ctx := &context{"redir", "", parse.Bareword, range0(p[0].Range().To)}
|
||||
items, err := generateFileNames("", false)
|
||||
return ctx, items, err
|
||||
}
|
||||
|
||||
expr := simpleExpr(ev)
|
||||
if np.match(expr, aRedir) {
|
||||
var expr np.SimpleExprData
|
||||
if p.Match(np.SimpleExpr(&expr, ev), np.Redir) {
|
||||
// Non-empty redirection target.
|
||||
ctx := &context{"redir", expr.s, expr.quote, expr.compound.Range()}
|
||||
items, err := generateFileNames(expr.s, false)
|
||||
ctx := &context{"redir", expr.Value, expr.PrimarType, expr.Compound.Range()}
|
||||
items, err := generateFileNames(expr.Value, false)
|
||||
return ctx, items, err
|
||||
}
|
||||
|
||||
return nil, nil, errNoCompletion
|
||||
}
|
||||
|
||||
func completeVariable(np nodePath, ev *eval.Evaler, cfg Config) (*context, []RawItem, error) {
|
||||
primary, ok := np[0].(*parse.Primary)
|
||||
func completeVariable(p np.Path, ev *eval.Evaler, cfg Config) (*context, []RawItem, error) {
|
||||
primary, ok := p[0].(*parse.Primary)
|
||||
if !ok || primary.Type != parse.Variable {
|
||||
return nil, nil, errNoCompletion
|
||||
}
|
||||
|
@ -152,7 +153,7 @@ func completeVariable(np nodePath, ev *eval.Evaler, cfg Config) (*context, []Raw
|
|||
diag.Ranging{From: begin, To: primary.Range().To}}
|
||||
|
||||
var items []RawItem
|
||||
eachVariableInNs(ev, np, ns, func(varname string) {
|
||||
eachVariableInNs(ev, p, ns, func(varname string) {
|
||||
items = append(items, noQuoteItem(parse.QuoteVariableName(varname)))
|
||||
})
|
||||
if ns == "" {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"src.elv.sh/pkg/eval/vals"
|
||||
"src.elv.sh/pkg/fsutil"
|
||||
"src.elv.sh/pkg/parse"
|
||||
"src.elv.sh/pkg/parse/np"
|
||||
"src.elv.sh/pkg/ui"
|
||||
)
|
||||
|
||||
|
@ -39,7 +40,7 @@ func GenerateForSudo(args []string, ev *eval.Evaler, cfg Config) ([]RawItem, err
|
|||
|
||||
// Internal generators, used from completers.
|
||||
|
||||
func generateArgs(args []string, ev *eval.Evaler, np nodePath, cfg Config) ([]RawItem, error) {
|
||||
func generateArgs(args []string, ev *eval.Evaler, p np.Path, cfg Config) ([]RawItem, error) {
|
||||
switch args[0] {
|
||||
case "set", "tmp":
|
||||
for _, arg := range args[1:] {
|
||||
|
@ -51,7 +52,7 @@ func generateArgs(args []string, ev *eval.Evaler, np nodePath, cfg Config) ([]Ra
|
|||
sigil, qname := eval.SplitSigil(seed)
|
||||
ns, _ := eval.SplitIncompleteQNameNs(qname)
|
||||
var items []RawItem
|
||||
eachVariableInNs(ev, np, ns, func(varname string) {
|
||||
eachVariableInNs(ev, p, ns, func(varname string) {
|
||||
items = append(items, noQuoteItem(sigil+parse.QuoteVariableName(ns+varname)))
|
||||
})
|
||||
return items, nil
|
||||
|
@ -71,7 +72,7 @@ func generateExternalCommands(seed string, ev *eval.Evaler) ([]RawItem, error) {
|
|||
return items, nil
|
||||
}
|
||||
|
||||
func generateCommands(seed string, ev *eval.Evaler, np nodePath) ([]RawItem, error) {
|
||||
func generateCommands(seed string, ev *eval.Evaler, p np.Path) ([]RawItem, error) {
|
||||
if fsutil.DontSearch(seed) {
|
||||
// Completing a local external command name.
|
||||
return generateFileNames(seed, true)
|
||||
|
@ -99,7 +100,7 @@ func generateCommands(seed string, ev *eval.Evaler, np nodePath) ([]RawItem, err
|
|||
ns, _ := eval.SplitIncompleteQNameNs(qname)
|
||||
if sigil == "" {
|
||||
// Generate functions, namespaces, and variable assignments.
|
||||
eachVariableInNs(ev, np, ns, func(varname string) {
|
||||
eachVariableInNs(ev, p, ns, func(varname string) {
|
||||
switch {
|
||||
case strings.HasSuffix(varname, eval.FnSuffix):
|
||||
addPlainItem(
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
package complete
|
||||
|
||||
import (
|
||||
"src.elv.sh/pkg/eval"
|
||||
"src.elv.sh/pkg/parse"
|
||||
)
|
||||
|
||||
type nodePath []parse.Node
|
||||
|
||||
// Returns the path of Node's from n to a leaf at position p. Leaf first in the
|
||||
// returned slice.
|
||||
func findNodePath(root parse.Node, p int) nodePath {
|
||||
n := root
|
||||
descend:
|
||||
for len(parse.Children(n)) > 0 {
|
||||
for _, ch := range parse.Children(n) {
|
||||
if rg := ch.Range(); rg.From <= p && p <= rg.To {
|
||||
n = ch
|
||||
continue descend
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var path []parse.Node
|
||||
for {
|
||||
path = append(path, n)
|
||||
if n == root {
|
||||
break
|
||||
}
|
||||
n = parse.Parent(n)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (ns nodePath) match(ms ...nodesMatcher) bool {
|
||||
for _, m := range ms {
|
||||
ns2, ok := m.matchNodes(ns)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ns = ns2
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type nodesMatcher interface {
|
||||
// Matches from the beginning of nodes. Returns the remaining nodes and
|
||||
// whether the match succeeded.
|
||||
matchNodes([]parse.Node) ([]parse.Node, bool)
|
||||
}
|
||||
|
||||
// Matches one node of a given type, without storing it.
|
||||
type typedMatcher[T parse.Node] struct{}
|
||||
|
||||
func (m typedMatcher[T]) matchNodes(ns []parse.Node) ([]parse.Node, bool) {
|
||||
if len(ns) > 0 {
|
||||
if _, ok := ns[0].(T); ok {
|
||||
return ns[1:], true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var (
|
||||
aChunk = typedMatcher[*parse.Chunk]{}
|
||||
aPipeline = typedMatcher[*parse.Pipeline]{}
|
||||
aArray = typedMatcher[*parse.Array]{}
|
||||
aRedir = typedMatcher[*parse.Redir]{}
|
||||
aSep = typedMatcher[*parse.Sep]{}
|
||||
)
|
||||
|
||||
// Matches one node of a certain type, and stores it into a pointer.
|
||||
type storeMatcher[T parse.Node] struct{ p *T }
|
||||
|
||||
func store[T parse.Node](p *T) nodesMatcher { return storeMatcher[T]{p} }
|
||||
|
||||
func (m storeMatcher[T]) matchNodes(ns []parse.Node) ([]parse.Node, bool) {
|
||||
if len(ns) > 0 {
|
||||
if n, ok := ns[0].(T); ok {
|
||||
*m.p = n
|
||||
return ns[1:], true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Matches an expression that can be evaluated statically. Consumes 3 nodes
|
||||
// (Primary, Indexing and Compound).
|
||||
type simpleExprMatcher struct {
|
||||
ev *eval.Evaler
|
||||
s string
|
||||
compound *parse.Compound
|
||||
quote parse.PrimaryType
|
||||
}
|
||||
|
||||
func simpleExpr(ev *eval.Evaler) *simpleExprMatcher {
|
||||
return &simpleExprMatcher{ev: ev}
|
||||
}
|
||||
|
||||
func (m *simpleExprMatcher) matchNodes(ns []parse.Node) ([]parse.Node, bool) {
|
||||
if len(ns) < 3 {
|
||||
return nil, false
|
||||
}
|
||||
primary, ok := ns[0].(*parse.Primary)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
indexing, ok := ns[1].(*parse.Indexing)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
compound, ok := ns[2].(*parse.Compound)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
s, ok := m.ev.PurelyEvalPartialCompound(compound, indexing.To)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
m.compound, m.quote, m.s = compound, primary.Type, s
|
||||
return ns[3:], true
|
||||
}
|
|
@ -7,18 +7,19 @@ import (
|
|||
"src.elv.sh/pkg/eval"
|
||||
"src.elv.sh/pkg/parse"
|
||||
"src.elv.sh/pkg/parse/cmpd"
|
||||
"src.elv.sh/pkg/parse/np"
|
||||
)
|
||||
|
||||
var environ = os.Environ
|
||||
|
||||
// Calls f for each variable name in namespace ns that can be found at the point
|
||||
// of np.
|
||||
func eachVariableInNs(ev *eval.Evaler, np nodePath, ns string, f func(s string)) {
|
||||
func eachVariableInNs(ev *eval.Evaler, p np.Path, ns string, f func(s string)) {
|
||||
switch ns {
|
||||
case "", ":":
|
||||
ev.Global().IterateKeysString(f)
|
||||
ev.Builtin().IterateKeysString(f)
|
||||
eachDefinedVariable(np[len(np)-1], np[0].Range().From, f)
|
||||
eachDefinedVariable(p[len(p)-1], p[0].Range().From, f)
|
||||
case "e:":
|
||||
eachExternal(func(cmd string) {
|
||||
f(cmd + eval.FnSuffix)
|
||||
|
|
|
@ -9,8 +9,10 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
lsp "github.com/sourcegraph/go-lsp"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/sourcegraph/jsonrpc2"
|
||||
lsp "pkg.nimblebun.works/go-lsp"
|
||||
"src.elv.sh/pkg/mods/doc"
|
||||
"src.elv.sh/pkg/must"
|
||||
"src.elv.sh/pkg/prog"
|
||||
"src.elv.sh/pkg/prog/progtest"
|
||||
|
@ -31,7 +33,7 @@ var diagTests = []struct {
|
|||
Range: lsp.Range{
|
||||
Start: lsp.Position{Line: 0, Character: 1},
|
||||
End: lsp.Position{Line: 0, Character: 2}},
|
||||
Severity: lsp.Error, Source: "parse", Message: "should be variable name",
|
||||
Severity: lsp.DSError, Source: "parse", Message: "should be variable name",
|
||||
},
|
||||
}},
|
||||
{"multi line with NL", "\n$!", []lsp.Diagnostic{
|
||||
|
@ -39,7 +41,7 @@ var diagTests = []struct {
|
|||
Range: lsp.Range{
|
||||
Start: lsp.Position{Line: 1, Character: 1},
|
||||
End: lsp.Position{Line: 1, Character: 2}},
|
||||
Severity: lsp.Error, Source: "parse", Message: "should be variable name",
|
||||
Severity: lsp.DSError, Source: "parse", Message: "should be variable name",
|
||||
},
|
||||
}},
|
||||
{"multi line with CR", "\r$!", []lsp.Diagnostic{
|
||||
|
@ -47,7 +49,7 @@ var diagTests = []struct {
|
|||
Range: lsp.Range{
|
||||
Start: lsp.Position{Line: 1, Character: 1},
|
||||
End: lsp.Position{Line: 1, Character: 2}},
|
||||
Severity: lsp.Error, Source: "parse", Message: "should be variable name",
|
||||
Severity: lsp.DSError, Source: "parse", Message: "should be variable name",
|
||||
},
|
||||
}},
|
||||
{"multi line with CRNL", "\r\n$!", []lsp.Diagnostic{
|
||||
|
@ -55,7 +57,7 @@ var diagTests = []struct {
|
|||
Range: lsp.Range{
|
||||
Start: lsp.Position{Line: 1, Character: 1},
|
||||
End: lsp.Position{Line: 1, Character: 2}},
|
||||
Severity: lsp.Error, Source: "parse", Message: "should be variable name",
|
||||
Severity: lsp.DSError, Source: "parse", Message: "should be variable name",
|
||||
},
|
||||
}},
|
||||
{"text with code point beyond FFFF", "\U00010000 $!", []lsp.Diagnostic{
|
||||
|
@ -63,7 +65,7 @@ var diagTests = []struct {
|
|||
Range: lsp.Range{
|
||||
Start: lsp.Position{Line: 0, Character: 4},
|
||||
End: lsp.Position{Line: 0, Character: 5}},
|
||||
Severity: lsp.Error, Source: "parse", Message: "should be variable name",
|
||||
Severity: lsp.DSError, Source: "parse", Message: "should be variable name",
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
@ -91,15 +93,68 @@ func TestDidChangeDiagnostics(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var hoverTests = []struct {
|
||||
name string
|
||||
text string
|
||||
pos lsp.Position
|
||||
|
||||
wantHover lsp.Hover
|
||||
}{
|
||||
{
|
||||
name: "command doc",
|
||||
text: "echo foo",
|
||||
pos: lsp.Position{Line: 0, Character: 0},
|
||||
|
||||
wantHover: hoverWith(must.OK1(doc.Source("echo"))),
|
||||
},
|
||||
{
|
||||
name: "variable doc",
|
||||
// 012345
|
||||
text: "echo $paths",
|
||||
pos: lsp.Position{Line: 0, Character: 5},
|
||||
|
||||
wantHover: hoverWith(must.OK1(doc.Source("$paths"))),
|
||||
},
|
||||
{
|
||||
name: "unknown command",
|
||||
text: "some-external",
|
||||
pos: lsp.Position{Line: 0, Character: 0},
|
||||
|
||||
wantHover: lsp.Hover{},
|
||||
},
|
||||
{
|
||||
name: "command at non-command position",
|
||||
// 012345678
|
||||
text: "echo echo",
|
||||
pos: lsp.Position{Line: 0, Character: 5},
|
||||
|
||||
wantHover: lsp.Hover{},
|
||||
},
|
||||
}
|
||||
|
||||
func hoverWith(markdown string) lsp.Hover {
|
||||
return lsp.Hover{Contents: lsp.MarkupContent{Kind: lsp.MKMarkdown, Value: markdown}}
|
||||
}
|
||||
|
||||
func TestHover(t *testing.T) {
|
||||
f := setup(t)
|
||||
|
||||
f.conn.Notify(bgCtx, "textDocument/didOpen", didOpenParams(""))
|
||||
// Hover is a no-op now; just check that it doesn't error.
|
||||
var hover lsp.Hover
|
||||
err := f.conn.Call(bgCtx, "textDocument/hover", struct{}{}, &hover)
|
||||
if err != nil {
|
||||
t.Errorf("got error %v", err)
|
||||
for _, test := range hoverTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
f.conn.Notify(bgCtx, "textDocument/didOpen", didOpenParams(test.text))
|
||||
request := lsp.TextDocumentPositionParams{
|
||||
TextDocument: lsp.TextDocumentIdentifier{URI: testURI},
|
||||
Position: test.pos,
|
||||
}
|
||||
var response lsp.Hover
|
||||
err := f.conn.Call(bgCtx, "textDocument/hover", request, &response)
|
||||
if err != nil {
|
||||
t.Errorf("got error %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(test.wantHover, response); diff != "" {
|
||||
t.Errorf("response (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,23 +198,31 @@ func TestCompletion(t *testing.T) {
|
|||
}
|
||||
|
||||
var jsonrpcErrorTests = []struct {
|
||||
name string
|
||||
method string
|
||||
params any
|
||||
wantErr error
|
||||
}{
|
||||
{"unknown/method", struct{}{}, errMethodNotFound},
|
||||
{"textDocument/didOpen", []int{}, errInvalidParams},
|
||||
{"textDocument/didChange", []int{}, errInvalidParams},
|
||||
{"textDocument/completion", []int{}, errInvalidParams},
|
||||
{"unknown method", "unknown/method", struct{}{}, errMethodNotFound},
|
||||
{"invalid request type", "textDocument/didOpen", []int{}, errInvalidParams},
|
||||
{"unknown document to hover", "textDocument/hover",
|
||||
lsp.TextDocumentPositionParams{
|
||||
TextDocument: lsp.TextDocumentIdentifier{URI: "file://unknown"}},
|
||||
unknownDocument("file://unknown")},
|
||||
{"unknown document to completion", "textDocument/completion",
|
||||
lsp.CompletionParams{
|
||||
TextDocumentPositionParams: lsp.TextDocumentPositionParams{
|
||||
TextDocument: lsp.TextDocumentIdentifier{URI: "file://unknown"}}},
|
||||
unknownDocument("file://unknown")},
|
||||
}
|
||||
|
||||
func TestJSONRPCErrors(t *testing.T) {
|
||||
f := setup(t)
|
||||
for _, test := range jsonrpcErrorTests {
|
||||
t.Run(test.method, func(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := f.conn.Call(context.Background(), test.method, test.params, &struct{}{})
|
||||
if err.Error() != test.wantErr.Error() {
|
||||
t.Errorf("got error %v, want %v", err, errMethodNotFound)
|
||||
t.Errorf("got error %v, want %v", err, test.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,13 +3,16 @@ package lsp
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
lsp "github.com/sourcegraph/go-lsp"
|
||||
"github.com/sourcegraph/jsonrpc2"
|
||||
lsp "pkg.nimblebun.works/go-lsp"
|
||||
"src.elv.sh/pkg/diag"
|
||||
"src.elv.sh/pkg/edit/complete"
|
||||
"src.elv.sh/pkg/eval"
|
||||
"src.elv.sh/pkg/mods/doc"
|
||||
"src.elv.sh/pkg/parse"
|
||||
"src.elv.sh/pkg/parse/np"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -20,12 +23,18 @@ var (
|
|||
)
|
||||
|
||||
type server struct {
|
||||
evaler *eval.Evaler
|
||||
content map[lsp.DocumentURI]string
|
||||
evaler *eval.Evaler
|
||||
documents map[lsp.DocumentURI]document
|
||||
}
|
||||
|
||||
type document struct {
|
||||
code string
|
||||
parseTree parse.Tree
|
||||
parseErr error
|
||||
}
|
||||
|
||||
func newServer() *server {
|
||||
return &server{eval.NewEvaler(), make(map[lsp.DocumentURI]string)}
|
||||
return &server{eval.NewEvaler(), make(map[lsp.DocumentURI]document)}
|
||||
}
|
||||
|
||||
func handler(s *server) jsonrpc2.Handler {
|
||||
|
@ -33,7 +42,7 @@ func handler(s *server) jsonrpc2.Handler {
|
|||
"initialize": s.initialize,
|
||||
"textDocument/didOpen": convertMethod(s.didOpen),
|
||||
"textDocument/didChange": convertMethod(s.didChange),
|
||||
"textDocument/hover": s.hover,
|
||||
"textDocument/hover": convertMethod(s.hover),
|
||||
"textDocument/completion": convertMethod(s.completion),
|
||||
|
||||
"textDocument/didClose": noop,
|
||||
|
@ -71,6 +80,8 @@ func routingHandler(methods map[string]method) jsonrpc2.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
// Can be used within handler implementations to recover the connection stored
|
||||
// in the Context.
|
||||
func conn(ctx context.Context) *jsonrpc2.Conn { return ctx.Value(connKey{}).(*jsonrpc2.Conn) }
|
||||
|
||||
// Handler implementations. These are all called synchronously.
|
||||
|
@ -78,21 +89,19 @@ func conn(ctx context.Context) *jsonrpc2.Conn { return ctx.Value(connKey{}).(*js
|
|||
func (s *server) initialize(_ context.Context, _ json.RawMessage) (any, error) {
|
||||
return &lsp.InitializeResult{
|
||||
Capabilities: lsp.ServerCapabilities{
|
||||
TextDocumentSync: &lsp.TextDocumentSyncOptionsOrKind{
|
||||
Options: &lsp.TextDocumentSyncOptions{
|
||||
OpenClose: true,
|
||||
Change: lsp.TDSKFull,
|
||||
},
|
||||
TextDocumentSync: &lsp.TextDocumentSyncOptions{
|
||||
OpenClose: true,
|
||||
Change: lsp.TDSyncKindFull,
|
||||
},
|
||||
CompletionProvider: &lsp.CompletionOptions{},
|
||||
HoverProvider: &lsp.HoverOptions{},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *server) didOpen(ctx context.Context, params lsp.DidOpenTextDocumentParams) (any, error) {
|
||||
uri, content := params.TextDocument.URI, params.TextDocument.Text
|
||||
s.content[uri] = content
|
||||
go publishDiagnostics(ctx, uri, content)
|
||||
s.updateDocument(conn(ctx), uri, content)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -100,21 +109,50 @@ func (s *server) didChange(ctx context.Context, params lsp.DidChangeTextDocument
|
|||
// ContentChanges includes full text since the server is only advertised to
|
||||
// support that; see the initialize method.
|
||||
uri, content := params.TextDocument.URI, params.ContentChanges[0].Text
|
||||
s.content[uri] = content
|
||||
go publishDiagnostics(ctx, uri, content)
|
||||
s.updateDocument(conn(ctx), uri, content)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *server) hover(_ context.Context, rawParams json.RawMessage) (any, error) {
|
||||
return lsp.Hover{}, nil
|
||||
func (s *server) hover(_ context.Context, params lsp.TextDocumentPositionParams) (any, error) {
|
||||
document, ok := s.documents[params.TextDocument.URI]
|
||||
if !ok {
|
||||
return nil, unknownDocument(params.TextDocument.URI)
|
||||
}
|
||||
pos := lspPositionToIdx(document.code, params.Position)
|
||||
|
||||
p := np.Find(document.parseTree.Root, pos)
|
||||
// Try variable doc
|
||||
var primary *parse.Primary
|
||||
if p.Match(np.Store(&primary)) && primary.Type == parse.Variable {
|
||||
// TODO: Take shadowing into consideration.
|
||||
markdown, err := doc.Source("$" + primary.Value)
|
||||
if err == nil {
|
||||
return lsp.Hover{Contents: lsp.MarkupContent{Kind: lsp.MKMarkdown, Value: markdown}}, nil
|
||||
}
|
||||
}
|
||||
// Try command doc
|
||||
var expr np.SimpleExprData
|
||||
var form *parse.Form
|
||||
if p.Match(np.SimpleExpr(&expr, nil), np.Store(&form)) && form.Head == expr.Compound {
|
||||
// TODO: Take shadowing into consideration.
|
||||
markdown, err := doc.Source(expr.Value)
|
||||
if err == nil {
|
||||
return lsp.Hover{Contents: lsp.MarkupContent{Kind: lsp.MKMarkdown, Value: markdown}}, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *server) completion(_ context.Context, params lsp.CompletionParams) (any, error) {
|
||||
content := s.content[params.TextDocument.URI]
|
||||
document, ok := s.documents[params.TextDocument.URI]
|
||||
if !ok {
|
||||
return nil, unknownDocument(params.TextDocument.URI)
|
||||
}
|
||||
code := document.code
|
||||
result, err := complete.Complete(
|
||||
complete.CodeBuffer{
|
||||
Content: content,
|
||||
Dot: lspPositionToIdx(content, params.Position)},
|
||||
Content: code,
|
||||
Dot: lspPositionToIdx(code, params.Position)},
|
||||
s.evaler,
|
||||
complete.Config{},
|
||||
)
|
||||
|
@ -124,7 +162,7 @@ func (s *server) completion(_ context.Context, params lsp.CompletionParams) (any
|
|||
}
|
||||
|
||||
lspItems := make([]lsp.CompletionItem, len(result.Items))
|
||||
lspRange := lspRangeFromRange(content, result.Replace)
|
||||
lspRange := lspRangeFromRange(code, result.Replace)
|
||||
var kind lsp.CompletionItemKind
|
||||
switch result.Name {
|
||||
case "command":
|
||||
|
@ -147,28 +185,31 @@ func (s *server) completion(_ context.Context, params lsp.CompletionParams) (any
|
|||
return lspItems, nil
|
||||
}
|
||||
|
||||
func publishDiagnostics(ctx context.Context, uri lsp.DocumentURI, content string) {
|
||||
conn(ctx).Notify(ctx, "textDocument/publishDiagnostics",
|
||||
lsp.PublishDiagnosticsParams{URI: uri, Diagnostics: diagnostics(uri, content)})
|
||||
func (s *server) updateDocument(conn *jsonrpc2.Conn, uri lsp.DocumentURI, code string) {
|
||||
tree, err := parse.Parse(parse.Source{Name: string(uri), Code: code}, parse.Config{})
|
||||
s.documents[uri] = document{code, tree, err}
|
||||
go func() {
|
||||
// Convert the parse error to lsp.Diagnostic objects and publish them.
|
||||
entries := parse.UnpackErrors(err)
|
||||
diags := make([]lsp.Diagnostic, len(entries))
|
||||
for i, err := range entries {
|
||||
diags[i] = lsp.Diagnostic{
|
||||
Range: lspRangeFromRange(code, err),
|
||||
Severity: lsp.DSError,
|
||||
Source: "parse",
|
||||
Message: err.Message,
|
||||
}
|
||||
}
|
||||
conn.Notify(context.Background(), "textDocument/publishDiagnostics",
|
||||
lsp.PublishDiagnosticsParams{URI: uri, Diagnostics: diags})
|
||||
}()
|
||||
}
|
||||
|
||||
func diagnostics(uri lsp.DocumentURI, content string) []lsp.Diagnostic {
|
||||
_, err := parse.Parse(parse.Source{Name: string(uri), Code: content}, parse.Config{})
|
||||
if err == nil {
|
||||
return []lsp.Diagnostic{}
|
||||
func unknownDocument(uri lsp.DocumentURI) error {
|
||||
return &jsonrpc2.Error{
|
||||
Code: jsonrpc2.CodeInvalidParams,
|
||||
Message: fmt.Sprintf("unknown document: %v", uri),
|
||||
}
|
||||
|
||||
entries := parse.UnpackErrors(err)
|
||||
diags := make([]lsp.Diagnostic, len(entries))
|
||||
for i, err := range entries {
|
||||
diags[i] = lsp.Diagnostic{
|
||||
Range: lspRangeFromRange(content, err),
|
||||
Severity: lsp.Error,
|
||||
Source: "parse",
|
||||
Message: err.Message,
|
||||
}
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
func lspRangeFromRange(s string, r diag.Ranger) lsp.Range {
|
||||
|
|
|
@ -33,7 +33,7 @@ var Ns = eval.BuildNsNamed("doc").
|
|||
AddGoFns(map[string]any{
|
||||
"show": show,
|
||||
"find": find,
|
||||
"source": source,
|
||||
"source": Source,
|
||||
"-symbols": symbols,
|
||||
}).
|
||||
Ns()
|
||||
|
@ -48,7 +48,7 @@ type showOptions struct{ Width int }
|
|||
func (opts *showOptions) SetDefaultOptions() {}
|
||||
|
||||
func show(fm *eval.Frame, opts showOptions, fqname string) error {
|
||||
doc, err := source(fqname)
|
||||
doc, err := Source(fqname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -103,7 +103,8 @@ func find(fm *eval.Frame, qs ...string) {
|
|||
}
|
||||
}
|
||||
|
||||
func source(fqname string) (string, error) {
|
||||
// Source returns the doc source for a symbol.
|
||||
func Source(fqname string) (string, error) {
|
||||
isVar := strings.HasPrefix(fqname, "$")
|
||||
if isVar {
|
||||
fqname = fqname[1:]
|
||||
|
|
|
@ -54,6 +54,21 @@ fn count {|str substr| }
|
|||
# ```
|
||||
fn equal-fold {|str1 str2| }
|
||||
|
||||
|
||||
# Splits `$str` around each instance of one or more consecutive white space
|
||||
# characters.
|
||||
#
|
||||
# ```elvish-transcript
|
||||
# ~> str:split "lorem ipsum dolor"
|
||||
# ▶ lorem
|
||||
# ▶ ipsum
|
||||
# ▶ dolor
|
||||
# ~> str:split " "
|
||||
# ```
|
||||
#
|
||||
# See also [`str:split`]().
|
||||
fn fields {|str| }
|
||||
|
||||
# Outputs a string consisting of the given Unicode codepoints. Example:
|
||||
#
|
||||
# ```elvish-transcript
|
||||
|
@ -184,7 +199,7 @@ fn replace {|&max=-1 old repl source| }
|
|||
# Etymology: Various languages, in particular
|
||||
# [Python](https://docs.python.org/3.6/library/stdtypes.html#str.split).
|
||||
#
|
||||
# See also [`str:join`]().
|
||||
# See also [`str:join`]() and [`str:fields`]().
|
||||
fn split {|&max=-1 sep string| }
|
||||
|
||||
# Outputs `$str` with all Unicode letters that begin words mapped to their
|
||||
|
|
|
@ -23,7 +23,8 @@ var Ns = eval.BuildNsNamed("str").
|
|||
"contains-any": strings.ContainsAny,
|
||||
"count": strings.Count,
|
||||
"equal-fold": strings.EqualFold,
|
||||
// TODO: Fields, FieldsFunc
|
||||
// TODO: FieldsFunc
|
||||
"fields": strings.Fields,
|
||||
"from-codepoints": fromCodepoints,
|
||||
"from-utf8-bytes": fromUtf8Bytes,
|
||||
"has-prefix": strings.HasPrefix,
|
||||
|
|
|
@ -35,6 +35,10 @@ func TestStr(t *testing.T) {
|
|||
That(`str:equal-fold abc ABC`).Puts(true),
|
||||
That(`str:equal-fold abc A`).Puts(false),
|
||||
|
||||
That(`str:fields "abc ABC"`).Puts("abc", "ABC"),
|
||||
That(`str:fields "abc ABC"`).Puts("abc", "ABC"),
|
||||
That(`str:fields " "`).Puts(),
|
||||
|
||||
That(`str:from-codepoints 0x61`).Puts("a"),
|
||||
That(`str:from-codepoints 0x4f60 0x597d`).Puts("你好"),
|
||||
That(`str:from-codepoints -0x1`).Throws(errs.OutOfRange{
|
||||
|
|
7
pkg/parse/cmpd/cmpd_test.go
Normal file
7
pkg/parse/cmpd/cmpd_test.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package cmpd
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test(t *testing.T) {
|
||||
// Test coverage of this package is provided by tests of its users.
|
||||
}
|
145
pkg/parse/np/np.go
Normal file
145
pkg/parse/np/np.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
// Package np provides utilities for working with node paths from a leaf of a
|
||||
// parse tree to the root.
|
||||
package np
|
||||
|
||||
import (
|
||||
"src.elv.sh/pkg/eval"
|
||||
"src.elv.sh/pkg/parse"
|
||||
)
|
||||
|
||||
// Path is a path from a leaf in a parse tree to the root.
|
||||
type Path []parse.Node
|
||||
|
||||
// Find finds the path of nodes from the leaf at position p to the root.
|
||||
func Find(root parse.Node, p int) Path { return find(root, p, false) }
|
||||
|
||||
// FindLeft finds the path of nodes from the leaf at position p to the root. If
|
||||
// p points to the start of one node (p == x.From), FindLeft finds the node to
|
||||
// the left instead (y s.t. p == y.To).
|
||||
func FindLeft(root parse.Node, p int) Path { return find(root, p, true) }
|
||||
|
||||
func find(root parse.Node, p int, preferLeft bool) Path {
|
||||
n := root
|
||||
descend:
|
||||
for len(parse.Children(n)) > 0 {
|
||||
for _, ch := range parse.Children(n) {
|
||||
r := ch.Range()
|
||||
if r.From <= p && p < r.To || preferLeft && p == r.To {
|
||||
n = ch
|
||||
continue descend
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var path []parse.Node
|
||||
for {
|
||||
path = append(path, n)
|
||||
if n == root {
|
||||
break
|
||||
}
|
||||
n = parse.Parent(n)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// Match matches against matchers, and returns whether all matches have
|
||||
// succeeded.
|
||||
func (p Path) Match(ms ...Matcher) bool {
|
||||
for _, m := range ms {
|
||||
p2, ok := m.Match(p)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
p = p2
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Matcher wraps the Match method.
|
||||
type Matcher interface {
|
||||
// Match takes a slice of nodes and returns the remaining nodes and whether
|
||||
// the match succeeded.
|
||||
Match([]parse.Node) ([]parse.Node, bool)
|
||||
}
|
||||
|
||||
// Typed returns a [Matcher] matching one node of a given type.
|
||||
func Typed[T parse.Node]() Matcher { return typedMatcher[T]{} }
|
||||
|
||||
// Commonly used [Typed] matchers.
|
||||
var (
|
||||
Chunk = Typed[*parse.Chunk]()
|
||||
Pipeline = Typed[*parse.Pipeline]()
|
||||
Array = Typed[*parse.Array]()
|
||||
Redir = Typed[*parse.Redir]()
|
||||
Sep = Typed[*parse.Sep]()
|
||||
)
|
||||
|
||||
type typedMatcher[T parse.Node] struct{}
|
||||
|
||||
func (m typedMatcher[T]) Match(ns []parse.Node) ([]parse.Node, bool) {
|
||||
if len(ns) > 0 {
|
||||
if _, ok := ns[0].(T); ok {
|
||||
return ns[1:], true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Store returns a [Matcher] matching one node of a given type, and stores it
|
||||
// if a match succeeds.
|
||||
func Store[T parse.Node](p *T) Matcher { return storeMatcher[T]{p} }
|
||||
|
||||
type storeMatcher[T parse.Node] struct{ p *T }
|
||||
|
||||
func (m storeMatcher[T]) Match(ns []parse.Node) ([]parse.Node, bool) {
|
||||
if len(ns) > 0 {
|
||||
if n, ok := ns[0].(T); ok {
|
||||
*m.p = n
|
||||
return ns[1:], true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// SimpleExpr returns a [Matcher] matching a "simple expression", which consists
|
||||
// of 3 nodes from the leaf upwards (Primary, Indexing and Compound) and where
|
||||
// the Compound expression can be evaluated statically using ev.
|
||||
func SimpleExpr(data *SimpleExprData, ev *eval.Evaler) Matcher {
|
||||
return simpleExprMatcher{data, ev}
|
||||
}
|
||||
|
||||
// SimpleExprData contains useful data written by the [SimpleExpr] matcher.
|
||||
type SimpleExprData struct {
|
||||
Value string
|
||||
Compound *parse.Compound
|
||||
PrimarType parse.PrimaryType
|
||||
}
|
||||
|
||||
type simpleExprMatcher struct {
|
||||
data *SimpleExprData
|
||||
ev *eval.Evaler
|
||||
}
|
||||
|
||||
func (m simpleExprMatcher) Match(ns []parse.Node) ([]parse.Node, bool) {
|
||||
if len(ns) < 3 {
|
||||
return nil, false
|
||||
}
|
||||
primary, ok := ns[0].(*parse.Primary)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
indexing, ok := ns[1].(*parse.Indexing)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
compound, ok := ns[2].(*parse.Compound)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
value, ok := m.ev.PurelyEvalPartialCompound(compound, indexing.To)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
*m.data = SimpleExprData{value, compound, primary.Type}
|
||||
return ns[3:], true
|
||||
}
|
7
pkg/parse/np/np_test.go
Normal file
7
pkg/parse/np/np_test.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package np
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test(t *testing.T) {
|
||||
// Test coverage of this package is provided by tests of its users.
|
||||
}
|
|
@ -149,32 +149,28 @@ func processHTMLID(s string) string {
|
|||
}
|
||||
|
||||
const tocBefore = `
|
||||
<div id="pandoc-toc-wrapper">
|
||||
<p>Table of Content: <span id="pandoc-toc-toggle-wrapper"></span></p>
|
||||
<div id="pandoc-toc">
|
||||
<div id="toc-wrapper">
|
||||
<div id="toc-header"><span id="toc-status"></span> Table of content</div>
|
||||
<div id="toc">
|
||||
`
|
||||
|
||||
const tocAfter = `
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var shown = true,
|
||||
tocToggleWrapper = document.getElementById('pandoc-toc-toggle-wrapper'),
|
||||
tocList = document.getElementById('pandoc-toc');
|
||||
var tocToggle = document.createElement('a');
|
||||
tocToggle.innerText = "[Hide]";
|
||||
tocToggle.href = "";
|
||||
tocToggleWrapper.appendChild(tocToggle);
|
||||
tocToggle.onclick = function(ev) {
|
||||
shown = !shown;
|
||||
if (shown) {
|
||||
tocToggle.innerText = "[Hide]";
|
||||
tocList.className = "";
|
||||
var open = true,
|
||||
tocHeader = document.getElementById('toc-header'),
|
||||
tocStatus = document.getElementById('toc-status'),
|
||||
tocList = document.getElementById('toc');
|
||||
tocHeader.onclick = function() {
|
||||
open = !open;
|
||||
if (open) {
|
||||
tocStatus.className = '';
|
||||
tocList.className = '';
|
||||
} else {
|
||||
tocToggle.innerText = "[Show]";
|
||||
tocList.className = "no-display";
|
||||
tocStatus.className = 'closed';
|
||||
tocList.className = 'no-display';
|
||||
}
|
||||
ev.preventDefault();
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
|
||||
const (
|
||||
terminalRows = 100
|
||||
terminalCols = 58
|
||||
terminalCols = 52
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -23,7 +23,7 @@ are supported:
|
|||
Now find your platform in the table, and download the corresponding binary
|
||||
archive:
|
||||
|
||||
<table>
|
||||
<table class="extra-wide">
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>amd64</th>
|
||||
|
@ -348,7 +348,7 @@ nix-env -i elvish
|
|||
The following old versions are no longer supported. They are only listed here
|
||||
for historical interest.
|
||||
|
||||
<table>
|
||||
<table class="extra-wide">
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>amd64</th>
|
||||
|
|
|
@ -17,13 +17,8 @@ Windows.
|
|||
<div class="demo-col demo-description">
|
||||
<h2>Powerful Pipelines</h2>
|
||||
<p>
|
||||
Text pipelines are intuitive and powerful. However, if your data have
|
||||
inherently complex structures, processing them with the pipeline
|
||||
often requires a lot of ad-hoc, hard-to-maintain text processing code.
|
||||
</p>
|
||||
<p>
|
||||
Pipelines in Elvish can carry structured data, not just text. You can
|
||||
stream lists, maps and even functions through the pipeline.
|
||||
Pipelines in Elvish can carry structured data, not just text. Stream
|
||||
lists, maps and even functions through the pipeline.
|
||||
</p>
|
||||
</div>
|
||||
<div class="demo-col demo-ttyshot">
|
||||
|
@ -35,14 +30,8 @@ Windows.
|
|||
<div class="demo-col demo-description">
|
||||
<h2>Intuitive Control Structures</h2>
|
||||
<p>
|
||||
If you know programming, you probably already know how
|
||||
<code>if</code> looks in C. So why learn another syntax?
|
||||
</p>
|
||||
<p>
|
||||
Elvish comes with a standard set of control structures: conditional
|
||||
control with <code>if</code>, loops with <code>for</code> and
|
||||
<code>while</code>, and exception handling with <code>try</code>. All
|
||||
of them have a familiar C-like syntax.
|
||||
Control structures in Elvish have a familiar C-like syntax. Never spell
|
||||
<code>if</code> backwards again.
|
||||
</p>
|
||||
</div>
|
||||
<div class="demo-col demo-ttyshot">
|
||||
|
@ -54,14 +43,8 @@ Windows.
|
|||
<div class="demo-col demo-description">
|
||||
<h2>Directory History</h2>
|
||||
<p>
|
||||
Do you type far too many <code>cd</code> commands? Do you struggle to
|
||||
remember which <code>deeply/nested/directory</code> your source codes,
|
||||
logs and configuration files are in?
|
||||
</p>
|
||||
<p>
|
||||
Backed by a real database, Elvish remembers all the directories you
|
||||
have been to, all the time. Just press <kbd>Ctrl-L</kbd>
|
||||
and search, as you do in a browser.
|
||||
Press <kbd>Ctrl-L</kbd> and jump to any directory you've been to.
|
||||
Type <code>cd java/com/lorem/ipsum</code> once and only once.
|
||||
</p>
|
||||
</div>
|
||||
<div class="demo-col demo-ttyshot">
|
||||
|
@ -73,14 +56,8 @@ Windows.
|
|||
<div class="demo-col demo-description">
|
||||
<h2>Command History</h2>
|
||||
<p>
|
||||
Want to find the magical <code>ffmpeg</code> command that you used to
|
||||
transcode a video file two months ago, but it is buried under a
|
||||
million other commands?
|
||||
</p>
|
||||
<p>
|
||||
No more cycling through history one command at a time.
|
||||
Press <kbd>Ctrl-R</kbd> and start searching your entire
|
||||
command history.
|
||||
Press <kbd>Ctrl-R</kbd> and find that beautiful <code>ffmpeg</code>
|
||||
command you used to transcode a video file two months ago.
|
||||
</p>
|
||||
</div>
|
||||
<div class="demo-col demo-ttyshot">
|
||||
|
@ -92,14 +69,8 @@ Windows.
|
|||
<div class="demo-col demo-description">
|
||||
<h2>Built-in File Manager</h2>
|
||||
<p>
|
||||
Want the convenience of a file manager, but can't give up the power of
|
||||
a shell?
|
||||
</p>
|
||||
<p>
|
||||
You no longer have to choose. Press
|
||||
<kbd>Ctrl-N</kbd> to start exploring directories and
|
||||
preview files, with the full power of a shell still under your
|
||||
fingertips.
|
||||
Press <kbd>Ctrl-N</kbd> to explore directories and preview files, with
|
||||
the full power of a shell still under your fingertips.
|
||||
</p>
|
||||
</div>
|
||||
<div class="demo-col demo-ttyshot">
|
||||
|
@ -128,7 +99,7 @@ Start your Elvish journey in this very website!
|
|||
|
||||
- Peruse the definitive [reference](ref/) documents
|
||||
|
||||
- Read the [blog](blog/) for news, tips, and developers' musings
|
||||
- Read the [blog](blog/) for the latest news
|
||||
|
||||
- Subscribe to the [feed](feed.atom) to keep updated
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
echo $x.pdf
|
||||
}
|
||||
~> try {
|
||||
fail 'bad error'
|
||||
fail 'something bad happened'
|
||||
} catch e {
|
||||
echo error $e
|
||||
echo (styled 'error:' red) $e[reason][content]
|
||||
} else {
|
||||
echo ok
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@ good
|
|||
lorem.pdf
|
||||
ipsum.pdf
|
||||
~> <span class="sgr-32">try</span> <span class="sgr-1">{</span>
|
||||
<span class="sgr-32">fail</span> <span class="sgr-33">'bad error'</span>
|
||||
<span class="sgr-1">}</span> catch e <span class="sgr-1">{</span>
|
||||
<span class="sgr-32">echo</span> error <span class="sgr-35">$e</span>
|
||||
<span class="sgr-1">}</span> else <span class="sgr-1">{</span>
|
||||
<span class="sgr-32">fail</span> <span class="sgr-33">'something bad happened'</span>
|
||||
<span class="sgr-1">}</span> <span class="sgr-33">catch</span> <span class="sgr-35">e</span> <span class="sgr-1">{</span>
|
||||
<span class="sgr-32">echo</span> <span class="sgr-1">(</span><span class="sgr-32">styled</span> <span class="sgr-33">'error:'</span> red<span class="sgr-1">)</span> <span class="sgr-35">$e</span><span class="sgr-1">[</span>reason<span class="sgr-1">][</span>content<span class="sgr-1">]</span>
|
||||
<span class="sgr-1">}</span> <span class="sgr-33">else</span> <span class="sgr-1">{</span>
|
||||
<span class="sgr-32">echo</span> ok
|
||||
<span class="sgr-1">}</span>
|
||||
error [&reason=[&content='bad error' &type=fail]]
|
||||
<span class="sgr-31">error:</span> something bad happened
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
~> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> HISTORY (dedup on) </span>
|
||||
3 echo "hello\nbye" > /tmp/x <span class="sgr-35">│</span>
|
||||
4 from-lines < /tmp/x <span class="sgr-35">│</span>
|
||||
5 cd /tmp <span class="sgr-7fg sgr-45"> </span>
|
||||
6 cd ~/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
7 git branch <span class="sgr-7fg sgr-45"> </span>
|
||||
8 git checkout . <span class="sgr-7fg sgr-45"> </span>
|
||||
9 git commit <span class="sgr-7fg sgr-45"> </span>
|
||||
19 git status <span class="sgr-7fg sgr-45"> </span>
|
||||
20 cd /usr/local/bin <span class="sgr-7fg sgr-45"> </span>
|
||||
21 echo $pwd <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-7fg sgr-7bg"> 22 * (+ 3 4) (- 100 94) </span><span class="sgr-7fg sgr-45"> </span>
|
||||
31 make <span class="sgr-7fg sgr-45"> </span>
|
||||
32 math:min 3 1 30 <span class="sgr-7fg sgr-45"> </span>
|
||||
~> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> HISTORY (dedup on) </span> <span class="sgr-7fg sgr-7bg">Ctrl-D</span> dedup
|
||||
3 echo "hello\nbye" > /tmp/x <span class="sgr-35">│</span>
|
||||
4 from-lines < /tmp/x <span class="sgr-35">│</span>
|
||||
5 cd /tmp <span class="sgr-7fg sgr-45"> </span>
|
||||
6 cd ~/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
7 git branch <span class="sgr-7fg sgr-45"> </span>
|
||||
8 git checkout . <span class="sgr-7fg sgr-45"> </span>
|
||||
9 git commit <span class="sgr-7fg sgr-45"> </span>
|
||||
19 git status <span class="sgr-7fg sgr-45"> </span>
|
||||
20 cd /usr/local/bin <span class="sgr-7fg sgr-45"> </span>
|
||||
21 echo $pwd <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-7fg sgr-7bg"> 22 * (+ 3 4) (- 100 94) </span><span class="sgr-7fg sgr-45"> </span>
|
||||
31 make <span class="sgr-7fg sgr-45"> </span>
|
||||
32 math:min 3 1 30 <span class="sgr-7fg sgr-45"> </span>
|
||||
|
|
|
@ -128,14 +128,8 @@ ul#demo-switcher > li > a:hover {
|
|||
|
||||
/* Overriding default styles */
|
||||
|
||||
.article h1 {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.article li > p {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
.content h1 {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
~> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> LOCATION </span>
|
||||
<span class="sgr-7fg sgr-7bg"> 10 ~/elvish </span><span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/.local/share/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/elvish/website <span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/.config/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
9 ~/elvish/pkg/edit <span class="sgr-7fg sgr-45"> </span>
|
||||
9 ~/elvish/pkg/eval <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /opt <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local/share <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local/bin <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /tmp <span class="sgr-35">│</span>
|
||||
8 ~/zsh <span class="sgr-35">│</span>
|
||||
<span class="sgr-7fg sgr-7bg"> 10 ~/elvish </span><span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/.local/share/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/elvish/website <span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/.config/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
9 ~/elvish/pkg/edit <span class="sgr-7fg sgr-45"> </span>
|
||||
9 ~/elvish/pkg/eval <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /opt <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local/share <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local/bin <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /tmp <span class="sgr-35">│</span>
|
||||
8 ~/zsh <span class="sgr-35">│</span>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
~/elvish> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> NAVIGATING </span>
|
||||
<span class="sgr-1 sgr-34"> bash </span> <span class="sgr-7fg sgr-7bg"> 1.0-release.md </span><span class="sgr-7fg sgr-45"> </span> 1.0 has not been released yet.
|
||||
<span class="sgr-7fg sgr-1 sgr-44"> elvish </span> CONTRIBUTING.md <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> zsh </span> Dockerfile <span class="sgr-7fg sgr-45"> </span>
|
||||
LICENSE <span class="sgr-7fg sgr-45"> </span>
|
||||
Makefile <span class="sgr-7fg sgr-45"> </span>
|
||||
PACKAGING.md <span class="sgr-7fg sgr-45"> </span>
|
||||
README.md <span class="sgr-7fg sgr-45"> </span>
|
||||
SECURITY.md <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> cmd </span><span class="sgr-7fg sgr-45"> </span>
|
||||
go.mod <span class="sgr-7fg sgr-45"> </span>
|
||||
go.sum <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> pkg </span><span class="sgr-35">│</span>
|
||||
<span class="sgr-1 sgr-34"> syntaxes </span><span class="sgr-35">│</span>
|
||||
~/elvish> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> NAVIGATING </span> <span class="sgr-7fg sgr-7bg">Ctrl-H</span> hidden <span class="sgr-7fg sgr-7bg">Ctrl-F</span> filter
|
||||
<span class="sgr-1 sgr-34"> bash </span> <span class="sgr-7fg sgr-7bg"> 1.0-release.m </span><span class="sgr-7fg sgr-45"> </span> 1.0 has not been released y
|
||||
<span class="sgr-7fg sgr-1 sgr-44"> elvis </span> CONTRIBUTING. <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> zsh </span> Dockerfile <span class="sgr-7fg sgr-45"> </span>
|
||||
LICENSE <span class="sgr-7fg sgr-45"> </span>
|
||||
Makefile <span class="sgr-7fg sgr-45"> </span>
|
||||
PACKAGING.md <span class="sgr-7fg sgr-45"> </span>
|
||||
README.md <span class="sgr-7fg sgr-45"> </span>
|
||||
SECURITY.md <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> cmd </span><span class="sgr-7fg sgr-45"> </span>
|
||||
go.mod <span class="sgr-7fg sgr-45"> </span>
|
||||
go.sum <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> pkg </span><span class="sgr-35">│</span>
|
||||
<span class="sgr-1 sgr-34"> syntaxes </span><span class="sgr-35">│</span>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
~> curl -sL api.github.com/repos/elves/elvish/issues |
|
||||
all (from-json) |
|
||||
each {|x| echo (exact-num $x[number]): $x[title] } |
|
||||
head -n 10
|
||||
~> range 1100 1111 |
|
||||
each {|x| curl -sL xkcd.com/$x/info.0.json } |
|
||||
from-json |
|
||||
each {|x| printf "%g: %s\n" $x[num] $x[title] }
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
~> <span class="sgr-32">curl</span> -sL api.github.com/repos/elves/elvish/issues <span class="sgr-32">|</span>
|
||||
<span class="sgr-32">all</span> <span class="sgr-1">(</span><span class="sgr-32">from-json</span><span class="sgr-1">)</span> <span class="sgr-32">|</span>
|
||||
<span class="sgr-32">each</span> <span class="sgr-1">{</span><span class="sgr-32">|</span>x<span class="sgr-32">|</span> <span class="sgr-32">echo</span> <span class="sgr-1">(</span><span class="sgr-32">exact-num</span> <span class="sgr-35">$x</span><span class="sgr-1">[</span>number<span class="sgr-1">])</span>: <span class="sgr-35">$x</span><span class="sgr-1">[</span>title<span class="sgr-1">]</span> <span class="sgr-1">}</span> <span class="sgr-32">|</span>
|
||||
<span class="sgr-32">head</span> -n 10
|
||||
1593: A mechanism to trap "interrupts" would be useful
|
||||
1592: Should `make style` implicitly run the `codespell` t
|
||||
arget?
|
||||
1591: Add a &benchmark option to the time command
|
||||
1590: Correct the documentation for the `try` command
|
||||
1588: Support comparing booleans
|
||||
1587: vi append command binding
|
||||
1586: Add a `&benchmark` option to the `time` command
|
||||
1585: boolean values are not comparable
|
||||
1584: Documentation fixups
|
||||
1583: Implement a `help` command
|
||||
~> <span class="sgr-32">range</span> 1100 1111 <span class="sgr-32">|</span>
|
||||
<span class="sgr-32">each</span> <span class="sgr-1">{</span><span class="sgr-32">|</span>x<span class="sgr-32">|</span> <span class="sgr-32">curl</span> -sL xkcd.com/<span class="sgr-35">$x</span>/info.0.json <span class="sgr-1">}</span> <span class="sgr-32">|</span>
|
||||
<span class="sgr-32">from-json</span> <span class="sgr-32">|</span>
|
||||
<span class="sgr-32">each</span> <span class="sgr-1">{</span><span class="sgr-32">|</span>x<span class="sgr-32">|</span> <span class="sgr-32">printf</span> <span class="sgr-33">"%g: %s\n"</span> <span class="sgr-35">$x</span><span class="sgr-1">[</span>num<span class="sgr-1">]</span> <span class="sgr-35">$x</span><span class="sgr-1">[</span>title<span class="sgr-1">]</span> <span class="sgr-1">}</span>
|
||||
1100: Vows
|
||||
1101: Sketchiness
|
||||
1102: Fastest-Growing
|
||||
1103: Nine
|
||||
1104: Feathers
|
||||
1105: License Plate
|
||||
1106: ADD
|
||||
1107: Sports Cheat Sheet
|
||||
1108: Cautionary Ghost
|
||||
1109: Refrigerator
|
||||
1110: Click and Drag
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
~> <span class="sgr-32">randint</span> 1 7
|
||||
▶ (num 6)
|
||||
~> <span class="sgr-4 sgr-32">randint</span><span class="sgr-4"> 1 7</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~> <span class="sgr-4 sgr-32">randint</span><span class="sgr-4"> 1 7</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> HISTORY #33 </span>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
~> <span class="sgr-32">randint</span> 1 7
|
||||
▶ (num 5)
|
||||
~> <span class="sgr-36"># more commands ...</span>
|
||||
~> <span class="sgr-32">ra</span><span class="sgr-4 sgr-32">ndint</span><span class="sgr-4"> 1 7</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~> <span class="sgr-32">ra</span><span class="sgr-4 sgr-32">ndint</span><span class="sgr-4"> 1 7</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> HISTORY #33 </span>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
~> <span class="sgr-32">cd</span> elvish
|
||||
~/elvish> <span class="sgr-32">echo</span> <span class="sgr-4">1.0-release.md </span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~/elvish> <span class="sgr-32">echo</span> <span class="sgr-4">1.0-release.md </span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> COMPLETING argument </span> .md
|
||||
<span class="sgr-7fg sgr-7bg">1.0-release.md </span> PACKAGING.md SECURITY.md
|
||||
CONTRIBUTING.md README.md
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
~> <span class="sgr-32">cd</span> elvish
|
||||
~/elvish> <span class="sgr-32">echo</span> <span class="sgr-4">1.0-release.md </span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~/elvish> <span class="sgr-32">echo</span> <span class="sgr-4">1.0-release.md </span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> COMPLETING argument </span>
|
||||
<span class="sgr-7fg sgr-7bg">1.0-release.md </span> README.md <span class="sgr-1 sgr-34">syntaxes/</span>
|
||||
CONTRIBUTING.md SECURITY.md <span class="sgr-1 sgr-34">tools/ </span>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
~> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> HISTORY (dedup on) </span>
|
||||
3 echo "hello\nbye" > /tmp/x <span class="sgr-35">│</span>
|
||||
4 from-lines < /tmp/x <span class="sgr-35">│</span>
|
||||
5 cd /tmp <span class="sgr-7fg sgr-45"> </span>
|
||||
6 cd ~/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
7 git branch <span class="sgr-7fg sgr-45"> </span>
|
||||
8 git checkout . <span class="sgr-7fg sgr-45"> </span>
|
||||
9 git commit <span class="sgr-7fg sgr-45"> </span>
|
||||
19 git status <span class="sgr-7fg sgr-45"> </span>
|
||||
20 cd /usr/local/bin <span class="sgr-7fg sgr-45"> </span>
|
||||
21 echo $pwd <span class="sgr-7fg sgr-45"> </span>
|
||||
22 * (+ 3 4) (- 100 94) <span class="sgr-7fg sgr-45"> </span>
|
||||
31 make <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-7fg sgr-7bg"> 32 math:min 3 1 30 </span><span class="sgr-7fg sgr-45"> </span>
|
||||
~> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> HISTORY (dedup on) </span> <span class="sgr-7fg sgr-7bg">Ctrl-D</span> dedup
|
||||
3 echo "hello\nbye" > /tmp/x <span class="sgr-35">│</span>
|
||||
4 from-lines < /tmp/x <span class="sgr-35">│</span>
|
||||
5 cd /tmp <span class="sgr-7fg sgr-45"> </span>
|
||||
6 cd ~/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
7 git branch <span class="sgr-7fg sgr-45"> </span>
|
||||
8 git checkout . <span class="sgr-7fg sgr-45"> </span>
|
||||
9 git commit <span class="sgr-7fg sgr-45"> </span>
|
||||
19 git status <span class="sgr-7fg sgr-45"> </span>
|
||||
20 cd /usr/local/bin <span class="sgr-7fg sgr-45"> </span>
|
||||
21 echo $pwd <span class="sgr-7fg sgr-45"> </span>
|
||||
22 * (+ 3 4) (- 100 94) <span class="sgr-7fg sgr-45"> </span>
|
||||
31 make <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-7fg sgr-7bg"> 32 math:min 3 1 30 </span><span class="sgr-7fg sgr-45"> </span>
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
~> <span class="sgr-32">echo</span><span class="sgr-4"> </span><span class="sgr-1 sgr-4">(</span><span class="sgr-4 sgr-32">styled</span><span class="sgr-4"> warning: red</span><span class="sgr-1 sgr-4">)</span><span class="sgr-4"> bumpy road</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~> <span class="sgr-32">echo</span><span class="sgr-4"> </span><span class="sgr-1 sgr-4">(</span><span class="sgr-4 sgr-32">styled</span><span class="sgr-4"> warning: red</span><span class="sgr-1 sgr-4">)</span><span class="sgr-4"> bumpy road</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> HISTORY #2 </span>
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
~> <span class="sgr-4 sgr-31">math:min</span><span class="sgr-4"> 3 1 30</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~> <span class="sgr-4 sgr-31">math:min</span><span class="sgr-4"> 3 1 30</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-7fg sgr-7bg">Ctrl-A</span> autofix: use math <span class="sgr-7fg sgr-7bg">Tab</span> <span class="sgr-7fg sgr-7bg">Enter</span> autofix first
|
||||
<span class="sgr-1 sgr-37 sgr-45"> HISTORY #32 </span>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
~> <span class="sgr-32">echo</span> abc def
|
||||
abc def
|
||||
~> <span class="sgr-32">vim</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~> <span class="sgr-32">vim</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> LASTCMD </span>
|
||||
<span class="sgr-7fg sgr-7bg"> echo abc def </span>
|
||||
<span class="sgr-7fg sgr-7bg"> echo abc def </span>
|
||||
0 echo
|
||||
1 abc
|
||||
2 def
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
~> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> LOCATION </span> local
|
||||
<span class="sgr-7fg sgr-7bg"> 10 ~/.local/share/elvish </span>
|
||||
<span class="sgr-7fg sgr-7bg"> 10 ~/.local/share/elvish </span>
|
||||
9 /usr/local
|
||||
9 /usr/local/share
|
||||
9 /usr/local/bin
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
~> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> LOCATION </span>
|
||||
<span class="sgr-7fg sgr-7bg"> 10 ~/elvish </span><span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/.local/share/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/elvish/website <span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/.config/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
9 ~/elvish/pkg/edit <span class="sgr-7fg sgr-45"> </span>
|
||||
9 ~/elvish/pkg/eval <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /opt <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local/share <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local/bin <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /tmp <span class="sgr-35">│</span>
|
||||
8 ~/zsh <span class="sgr-35">│</span>
|
||||
<span class="sgr-7fg sgr-7bg"> 10 ~/elvish </span><span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/.local/share/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/elvish/website <span class="sgr-7fg sgr-45"> </span>
|
||||
10 ~/.config/elvish <span class="sgr-7fg sgr-45"> </span>
|
||||
9 ~/elvish/pkg/edit <span class="sgr-7fg sgr-45"> </span>
|
||||
9 ~/elvish/pkg/eval <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /opt <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local/share <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr/local/bin <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /usr <span class="sgr-7fg sgr-45"> </span>
|
||||
9 /tmp <span class="sgr-35">│</span>
|
||||
8 ~/zsh <span class="sgr-35">│</span>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
~/elvish> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> NAVIGATING </span>
|
||||
<span class="sgr-1 sgr-34"> bash </span> <span class="sgr-7fg sgr-7bg"> 1.0-release.md </span><span class="sgr-7fg sgr-45"> </span> 1.0 has not been released yet.
|
||||
<span class="sgr-7fg sgr-1 sgr-44"> elvish </span> CONTRIBUTING.md <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> zsh </span> Dockerfile <span class="sgr-7fg sgr-45"> </span>
|
||||
LICENSE <span class="sgr-7fg sgr-45"> </span>
|
||||
Makefile <span class="sgr-7fg sgr-45"> </span>
|
||||
PACKAGING.md <span class="sgr-7fg sgr-45"> </span>
|
||||
README.md <span class="sgr-7fg sgr-45"> </span>
|
||||
SECURITY.md <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> cmd </span><span class="sgr-7fg sgr-45"> </span>
|
||||
go.mod <span class="sgr-7fg sgr-45"> </span>
|
||||
go.sum <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> pkg </span><span class="sgr-35">│</span>
|
||||
<span class="sgr-1 sgr-34"> syntaxes </span><span class="sgr-35">│</span>
|
||||
~/elvish> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> NAVIGATING </span> <span class="sgr-7fg sgr-7bg">Ctrl-H</span> hidden <span class="sgr-7fg sgr-7bg">Ctrl-F</span> filter
|
||||
<span class="sgr-1 sgr-34"> bash </span> <span class="sgr-7fg sgr-7bg"> 1.0-release.m </span><span class="sgr-7fg sgr-45"> </span> 1.0 has not been released y
|
||||
<span class="sgr-7fg sgr-1 sgr-44"> elvis </span> CONTRIBUTING. <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> zsh </span> Dockerfile <span class="sgr-7fg sgr-45"> </span>
|
||||
LICENSE <span class="sgr-7fg sgr-45"> </span>
|
||||
Makefile <span class="sgr-7fg sgr-45"> </span>
|
||||
PACKAGING.md <span class="sgr-7fg sgr-45"> </span>
|
||||
README.md <span class="sgr-7fg sgr-45"> </span>
|
||||
SECURITY.md <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> cmd </span><span class="sgr-7fg sgr-45"> </span>
|
||||
go.mod <span class="sgr-7fg sgr-45"> </span>
|
||||
go.sum <span class="sgr-7fg sgr-45"> </span>
|
||||
<span class="sgr-1 sgr-34"> pkg </span><span class="sgr-35">│</span>
|
||||
<span class="sgr-1 sgr-34"> syntaxes </span><span class="sgr-35">│</span>
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
<span class="sgr-32">tilde-abbr</span> <span class="sgr-35">$pwd</span>
|
||||
<span class="sgr-32">styled</span> <span class="sgr-33">'❱ '</span> bright-red
|
||||
<span class="sgr-1">}</span>
|
||||
~<span class="sgr-91">❱ </span><span class="sgr-36"># Fancy unicode prompts!</span> <span class="sgr-7fg sgr-7bg">elf✸host.example.com</span>
|
||||
~<span class="sgr-91">❱ </span><span class="sgr-36"># Fancy unicode prompts!</span> <span class="sgr-7fg sgr-7bg">elf✸host.example.com</span>
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
~> <span class="sgr-31">str:</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~> <span class="sgr-31">str:</span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-7fg sgr-7bg">Ctrl-A</span> autofix: use str <span class="sgr-7fg sgr-7bg">Tab</span> <span class="sgr-7fg sgr-7bg">Enter</span> autofix first
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
~/elvish> <span class="sgr-32">vim</span> <span class="sgr-4">1.0-release.md </span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
~/elvish> <span class="sgr-32">vim</span> <span class="sgr-4">1.0-release.md </span> <span class="sgr-7fg sgr-7bg">elf@host</span>
|
||||
<span class="sgr-1 sgr-37 sgr-45"> COMPLETING argument </span>
|
||||
<span class="sgr-7fg sgr-7bg">1.0-release.md </span> README.md <span class="sgr-1 sgr-34">syntaxes/</span>
|
||||
CONTRIBUTING.md SECURITY.md <span class="sgr-1 sgr-34">tools/ </span>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
The latest version of documents in this section are also available as a
|
||||
[docset](https://elv.sh/ref/docset/Elvish.tgz). You can also
|
||||
[subscribe](dash-feed://https%3A%2F%2Felv.sh%2Fref%2Fdocset%2FElvish.xml) to the
|
||||
feed.
|
||||
Reference documents also available as a docset. Download the
|
||||
[latest build](https://elv.sh/ref/docset/Elvish.tgz) or subscribe to the
|
||||
[feed](dash-feed://https%3A%2F%2Felv.sh%2Fref%2Fdocset%2FElvish.xml).
|
||||
|
|
|
@ -10,7 +10,7 @@ html {
|
|||
body {
|
||||
font-family: "Source Serif", Georgia, serif;
|
||||
font-size: 17px;
|
||||
line-height: 1.4em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
body.has-js .no-js, body.no-js .has-js {
|
||||
|
@ -29,7 +29,7 @@ a {
|
|||
/**
|
||||
* Top-level elements.
|
||||
*
|
||||
* There are two main elements: #navbar and #content. Both have a maximum
|
||||
* There are two main elements: #navbar and .content. Both have a maximum
|
||||
* width, and is centered when the viewport is wider than that.
|
||||
*
|
||||
* #navbar is wrapped by #navbar-container, a black stripe that always span
|
||||
|
@ -40,13 +40,29 @@ a {
|
|||
width: 100%;
|
||||
color: white;
|
||||
background-color: #1a1a1a;
|
||||
padding: 12px 0;
|
||||
padding: 7px 0;
|
||||
}
|
||||
|
||||
#content, #navbar {
|
||||
max-width: 1024px;
|
||||
.content, #navbar {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 4%;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
/*
|
||||
832px = max-width + left and right padding of .content.
|
||||
|
||||
After this screen width, .content will no longer get wider, but we allow
|
||||
.extra-wide elements to continue to get wider up to 900px, using negative
|
||||
left and right margins.
|
||||
*/
|
||||
@media screen and (min-width: 832px) {
|
||||
.extra-wide {
|
||||
/* 32px is left and right padding of .content. */
|
||||
width: calc(min(100vw - 32px, 900px));
|
||||
/* upper bound is calculated by substituting 100vw = 900px + 32px */
|
||||
margin-inline: calc(max((832px - 100vw) / 2, -50px));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,13 +75,12 @@ a {
|
|||
#site-title, #nav-list {
|
||||
display: inline-block;
|
||||
/* Add spacing between lines when the navbar cannot fit in one line. */
|
||||
line-height: 1.4em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
#site-title {
|
||||
font-size: 1.2em;
|
||||
margin-right: 0.6em;
|
||||
/* Move the title upward 1px so that it looks more aligned with the
|
||||
/* Move the title downward 1px so that it looks more aligned with the
|
||||
* category list. */
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
@ -75,17 +90,23 @@ a {
|
|||
color: #5b5;
|
||||
}
|
||||
|
||||
#nav-list {
|
||||
margin-left: 0.8em;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.nav-link > code {
|
||||
padding: 0px 0.5em;
|
||||
padding: 0px 0.4em;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
|
@ -97,10 +118,6 @@ a {
|
|||
background-color: white;
|
||||
}
|
||||
|
||||
.nav-item + .nav-item::before {
|
||||
content: "|";
|
||||
}
|
||||
|
||||
/**
|
||||
* Article header.
|
||||
**/
|
||||
|
@ -111,12 +128,12 @@ a {
|
|||
|
||||
.article-title {
|
||||
padding: 16px 0;
|
||||
border-bottom: solid 1px #667;
|
||||
border-bottom: darkred solid 2px;
|
||||
}
|
||||
|
||||
/* Extra level needed to be more specific than .article h1 */
|
||||
.article .article-title h1 {
|
||||
font-size: 1.5em;
|
||||
/* Override .content h1 */
|
||||
.content .article-title h1 {
|
||||
font-size: 1.4em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
|
@ -130,30 +147,26 @@ a {
|
|||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.article p, .article ul, .article pre {
|
||||
margin-bottom: 16px;
|
||||
.content p, .content ul, .content pre {
|
||||
margin-bottom: 0.7em;
|
||||
}
|
||||
|
||||
.article li {
|
||||
.content li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.article li > p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
/* Block code. */
|
||||
.article pre {
|
||||
.content pre {
|
||||
padding: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Inline code. */
|
||||
.article p code {
|
||||
.content p code {
|
||||
padding: 0.1em 0;
|
||||
}
|
||||
|
||||
.article p code::before, .article p code::after {
|
||||
.content p code::before, .content p code::after {
|
||||
letter-spacing: -0.2em;
|
||||
content: "\00a0";
|
||||
}
|
||||
|
@ -162,116 +175,129 @@ code, pre {
|
|||
font-family: "Fira Mono", Menlo, "Roboto Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
.article code, .article pre {
|
||||
.content code, .content pre {
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* This doesn't have p, so that it also applies to ttyshots. */
|
||||
.article code {
|
||||
.content code {
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
/* We only use h1 to h3. */
|
||||
|
||||
.article h1, .article h2, .article h3 {
|
||||
.content h1, .content h2, .content h3 {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.article h1, .article h2, .article h3 {
|
||||
.content h1, .content h2, .content h3 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.article h1 {
|
||||
.content h1 {
|
||||
font-size: 1.3em;
|
||||
padding-bottom: 0.4em;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
.article h2 {
|
||||
.content h1 {
|
||||
border-left: darkred solid 0.3em;
|
||||
padding-left: 0.3em;
|
||||
margin-left: -0.6em;
|
||||
}
|
||||
|
||||
.content h2 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.article h3 {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.article ul, .article ol {
|
||||
margin-left: 1em;
|
||||
.content ul, .content ol {
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table of content.
|
||||
*/
|
||||
|
||||
#pandoc-toc-wrapper {
|
||||
#toc-wrapper {
|
||||
background-color: #f0f0f0;
|
||||
padding: 1em;
|
||||
margin: 0 16px 16px 0;
|
||||
margin: 0 0 16px 0;
|
||||
border-radius: 6px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* The first <h1> clears the TOC */
|
||||
.article-content h1 {
|
||||
clear: both;
|
||||
#toc-header {
|
||||
padding: 1em 1em 0.6em 1em;
|
||||
border-bottom: solid white 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#pandoc-toc {
|
||||
#toc-status {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-right: 2px;
|
||||
border: 6px solid transparent;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#toc-status:not(.closed) {
|
||||
border-top: 6px solid black;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
#toc-status.closed {
|
||||
border-left: 6px solid black;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
#toc {
|
||||
margin-left: -0.6em;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) and (max-width: 899px) {
|
||||
#pandoc-toc {
|
||||
@media screen and (min-width: 600px) {
|
||||
#toc {
|
||||
column-count: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 900px) {
|
||||
#pandoc-toc {
|
||||
column-count: 3;
|
||||
}
|
||||
/* Override value from .content ul, which is too big for the ToC */
|
||||
#toc ul {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
#pandoc-toc li {
|
||||
#toc li {
|
||||
list-style: none;
|
||||
/* Keep first-level ToC within one column */
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/*
|
||||
When the ToC can have two columns, the first item of the second column will not
|
||||
the intended top margin (this is how columns work in CSS). Work around this by
|
||||
adding some extra top padding in #toc, and removing the top margin of the very
|
||||
first <li> element to match.
|
||||
*/
|
||||
#toc > ul:first-child > li:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Category content.
|
||||
**/
|
||||
|
||||
#content.category {
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.category-prelude, .group-intro, .article-list {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.article-list > li {
|
||||
list-style: square inside;
|
||||
padding: 3px;
|
||||
.content.category {
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.article-list > li:hover {
|
||||
background-color: #c0c0c0;
|
||||
}
|
||||
|
||||
.article-link, .article-link:visited {
|
||||
color: black;
|
||||
display: inline;
|
||||
line-height: 1.4em;
|
||||
border-bottom: 1px solid black;
|
||||
background-color: #d0d0d0;
|
||||
}
|
||||
|
||||
.article-timestamp {
|
||||
float: right;
|
||||
display: inline-block;
|
||||
display: block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
|
@ -319,11 +345,6 @@ kbd {
|
|||
font-family: "Lucida Grande", Arial, sans-serif;
|
||||
}
|
||||
|
||||
/** Section numbers generated by pandoc */
|
||||
.header-section-number:after, .toc-section-number:after {
|
||||
content: ".";
|
||||
}
|
||||
|
||||
/**
|
||||
* TTY shots.
|
||||
*/
|
||||
|
@ -456,11 +477,11 @@ pre.ttyshot, pre.ttyshot code {
|
|||
background-color: #333;
|
||||
}
|
||||
|
||||
.dark .article code, .dark .article pre {
|
||||
.dark .content code, .dark .content pre {
|
||||
background-color: #181818;
|
||||
}
|
||||
|
||||
.dark #pandoc-toc-wrapper {
|
||||
.dark #toc-wrapper {
|
||||
background-color: #181818;
|
||||
}
|
||||
|
||||
|
|
|
@ -162,8 +162,8 @@
|
|||
</html>
|
||||
|
||||
{{ define "article-content" }}
|
||||
<div id="content">
|
||||
<article class="article">
|
||||
<div class="content">
|
||||
<article>
|
||||
{{ if not .IsHomepage }}
|
||||
<div class="article-title">
|
||||
<div class="timestamp"> {{ .Timestamp }} </div>
|
||||
|
@ -181,15 +181,15 @@
|
|||
|
||||
{{ define "category-content" }}
|
||||
{{ $category := .Category }}
|
||||
<div id="content" class="category">
|
||||
<div class="content category">
|
||||
{{ if .Prelude }}
|
||||
<article class="category-prelude article">
|
||||
<article>
|
||||
{{ .Prelude }}
|
||||
</article>
|
||||
{{ end }}
|
||||
{{ range $group := .Groups }}
|
||||
{{ if $group.Intro }}
|
||||
<div class="group-intro">{{ $group.Intro }}</div>
|
||||
<p>{{ $group.Intro }}</p>
|
||||
{{ end }}
|
||||
<ul class="article-list">
|
||||
{{ range $article := $group.Articles }}
|
||||
|
@ -202,7 +202,6 @@
|
|||
<span class="article-timestamp">
|
||||
{{ $article.Timestamp }}
|
||||
</span>
|
||||
<div class="clear"></div>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
|
Loading…
Reference in New Issue
Block a user