From b152481e452c2c0d5a904e461a3f25e4a7120ec1 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Wed, 4 Apr 2018 11:38:44 +0100 Subject: [PATCH 001/186] sync margo after the move to margo.sh --- gosubl/margo.py | 2 +- gosubl/margo_agent.py | 7 +- src/disposa.blue/margo/cmd/margo/main.go | 9 - src/disposa.blue/margo/cmdpkg/margo/main.go | 15 - src/disposa.blue/margo/mg/log.go | 10 - src/disposa.blue/margo/sublime/sublime.go | 105 ------ .../margo => margo.sh}/.gitignore | 0 .../margo => margo.sh}/.travis.yml | 2 +- src/{disposa.blue/margo => margo.sh}/AUTHORS | 0 .../margo => margo.sh}/Gopkg.lock | 10 +- .../margo => margo.sh}/Gopkg.toml | 2 +- src/{disposa.blue/margo => margo.sh}/LICENSE | 0 .../margo => margo.sh}/README.md | 0 .../cmd/margo.sublime/main.go | 2 +- .../cmdpkg/margo/cmdrunner/cmdrunner.go | 64 ++++ src/margo.sh/cmdpkg/margo/main.go | 95 +++++ .../cmdpkg/margosublime/main-extension.go | 0 .../cmdpkg/margosublime/main.go | 8 +- .../extension-example/extension-example.go | 4 +- .../extension-example/extension_test.go | 2 +- .../margo => margo.sh}/golang/common.go | 2 +- .../margo => margo.sh}/golang/completion.go | 2 +- .../golang/completion_test.go | 0 .../margo => margo.sh}/golang/gocode.go | 6 +- .../margo => margo.sh}/golang/gofmt.go | 4 +- .../margo => margo.sh}/golang/golang.go | 2 +- .../golang/internal/gocode/.gitignore | 0 .../golang/internal/gocode/LICENSE | 0 .../golang/internal/gocode/README.md | 0 .../internal/gocode/autocompletecontext.go | 0 .../internal/gocode/autocompletefile.go | 0 .../golang/internal/gocode/bridge._margo_.go | 0 .../golang/internal/gocode/client.go | 0 .../golang/internal/gocode/config.go | 0 .../golang/internal/gocode/cursorcontext.go | 0 .../golang/internal/gocode/decl.go | 0 .../golang/internal/gocode/declcache.go | 0 .../golang/internal/gocode/formatters.go | 0 .../internal/gocode/generate._margo_.go | 0 .../golang/internal/gocode/gocode.go | 0 .../golang/internal/gocode/os_posix.go | 0 .../golang/internal/gocode/os_windows.go | 0 .../golang/internal/gocode/package.go | 0 .../golang/internal/gocode/package_bin.go | 0 .../golang/internal/gocode/package_text.go | 0 .../golang/internal/gocode/pre_go17.go | 0 .../golang/internal/gocode/ripper.go | 0 .../golang/internal/gocode/rpc.go | 0 .../golang/internal/gocode/scope.go | 0 .../golang/internal/gocode/server.go | 0 .../gocode/type_alias_build_hack_18.go | 0 .../gocode/type_alias_build_hack_19.go | 0 .../golang/internal/gocode/utils.go | 0 .../margo => margo.sh}/golang/lint.go | 2 +- .../margo => margo.sh}/golang/parse.go | 2 +- .../margo => margo.sh}/golang/parse_test.go | 0 .../margo => margo.sh}/golang/snippets.go | 2 +- .../margo => margo.sh}/golang/syntaxcheck.go | 2 +- .../margo => margo.sh}/golang/version.go | 0 .../margo => margo.sh}/golang/version_test.go | 0 .../margo => margo.sh}/js/jsonfmt.go | 2 +- src/margo.sh/main.go | 9 + .../margo => margo.sh}/mg/action.go | 0 .../margo => margo.sh}/mg/agent.go | 14 +- .../margo => margo.sh}/mg/agent_test.go | 0 .../margo => margo.sh}/mg/client-actions.go | 0 .../margo => margo.sh}/mg/common.go | 0 .../margo => margo.sh}/mg/completion.go | 0 src/{disposa.blue/margo => margo.sh}/mg/db.go | 0 src/margo.sh/mg/doc.go | 1 + .../margo => margo.sh}/mg/extension.go | 0 .../margo => margo.sh}/mg/issue.go | 0 .../margo => margo.sh}/mg/issue_test.go | 0 src/margo.sh/mg/log.go | 18 + .../margo => margo.sh}/mg/reducers.go | 49 ++- .../margo => margo.sh}/mg/state.go | 6 +- .../margo => margo.sh}/mg/store.go | 0 .../margo => margo.sh}/mg/tasks.go | 0 .../margo => margo.sh}/mg/tempdir.go | 0 .../margo => margo.sh}/mg/tooltips.go | 0 .../margo => margo.sh}/mg/view.go | 0 .../margo => margo.sh}/mg/view_test.go | 0 .../margo => margo.sh}/mgcli/mgcli.go | 6 + .../misc/pprof/pprofdo/pprofdo-fallback.go | 0 .../misc/pprof/pprofdo/pprofdo.go | 0 .../misc/pprof/pprofhttp/pprofhttp.go | 0 .../margo => margo.sh}/sublime/config.go | 2 +- .../margo => margo.sh}/sublime/ext.go | 2 +- src/margo.sh/sublime/sublime.go | 125 +++++++ .../vendor/github.com/ugorji/go/LICENSE | 0 .../vendor/github.com/ugorji/go/codec/0doc.go | 0 .../github.com/ugorji/go/codec/README.md | 0 .../vendor/github.com/ugorji/go/codec/binc.go | 0 .../vendor/github.com/ugorji/go/codec/cbor.go | 0 .../github.com/ugorji/go/codec/decode.go | 0 .../github.com/ugorji/go/codec/encode.go | 0 .../ugorji/go/codec/fast-path.generated.go | 0 .../ugorji/go/codec/fast-path.go.tmpl | 0 .../ugorji/go/codec/fast-path.not.go | 0 .../ugorji/go/codec/gen-dec-array.go.tmpl | 0 .../ugorji/go/codec/gen-dec-map.go.tmpl | 0 .../ugorji/go/codec/gen-enc-chan.go.tmpl | 0 .../ugorji/go/codec/gen-helper.generated.go | 0 .../ugorji/go/codec/gen-helper.go.tmpl | 0 .../ugorji/go/codec/gen.generated.go | 0 .../vendor/github.com/ugorji/go/codec/gen.go | 0 .../go/codec/goversion_arrayof_gte_go15.go | 0 .../go/codec/goversion_arrayof_lt_go15.go | 0 .../go/codec/goversion_makemap_gte_go19.go | 0 .../go/codec/goversion_makemap_lt_go19.go | 0 ...version_unexportedembeddedptr_gte_go110.go | 0 ...oversion_unexportedembeddedptr_lt_go110.go | 0 .../go/codec/goversion_unsupported_lt_go14.go | 0 .../go/codec/goversion_vendor_eq_go15.go | 0 .../go/codec/goversion_vendor_eq_go16.go | 0 .../go/codec/goversion_vendor_gte_go17.go | 0 .../go/codec/goversion_vendor_lt_go15.go | 0 .../github.com/ugorji/go/codec/helper.go | 0 .../ugorji/go/codec/helper_internal.go | 0 .../ugorji/go/codec/helper_not_unsafe.go | 0 .../ugorji/go/codec/helper_unsafe.go | 0 .../vendor/github.com/ugorji/go/codec/json.go | 0 .../ugorji/go/codec/mammoth-test.go.tmpl | 0 .../ugorji/go/codec/mammoth2-test.go.tmpl | 0 .../github.com/ugorji/go/codec/msgpack.go | 0 .../vendor/github.com/ugorji/go/codec/rpc.go | 0 .../github.com/ugorji/go/codec/simple.go | 0 .../ugorji/go/codec/test-cbor-goldens.json | 0 .../vendor/github.com/ugorji/go/codec/test.py | 0 .../vendor/github.com/ugorji/go/codec/xml.go | 0 .../vendor/github.com/urfave/cli/.flake8 | 0 .../vendor/github.com/urfave/cli/.gitignore | 0 .../vendor/github.com/urfave/cli/.travis.yml | 0 .../vendor/github.com/urfave/cli/CHANGELOG.md | 0 .../github.com/urfave/cli/CODE_OF_CONDUCT.md | 74 ++++ .../github.com/urfave/cli/CONTRIBUTING.md | 19 + .../vendor/github.com/urfave/cli/LICENSE | 0 .../github.com/urfave/cli/MAINTAINERS.md | 1 + .../vendor/github.com/urfave/cli/README.md | 231 +++++++++--- .../vendor/github.com/urfave/cli/app.go | 31 +- .../vendor/github.com/urfave/cli/appveyor.yml | 0 .../vendor/github.com/urfave/cli/category.go | 2 +- .../vendor/github.com/urfave/cli/cli.go | 0 .../vendor/github.com/urfave/cli/command.go | 144 +++++--- .../vendor/github.com/urfave/cli/context.go | 31 +- .../vendor/github.com/urfave/cli/errors.go | 0 .../github.com/urfave/cli/flag-types.json | 0 .../vendor/github.com/urfave/cli/flag.go | 335 +++++++++--------- .../github.com/urfave/cli/flag_generated.go | 53 +-- .../vendor/github.com/urfave/cli/funcs.go | 16 + .../github.com/urfave/cli/generate-flag-types | 1 + .../vendor/github.com/urfave/cli/help.go | 11 +- .../vendor/github.com/urfave/cli/runtests | 0 .../vendor/github.com/urfave/cli/sort.go | 29 ++ .../vendor/golang.org/x/crypto/AUTHORS | 0 .../vendor/golang.org/x/crypto/CONTRIBUTORS | 0 .../vendor/golang.org/x/crypto/LICENSE | 0 .../vendor/golang.org/x/crypto/PATENTS | 0 .../golang.org/x/crypto/blake2b/blake2b.go | 68 ++++ .../x/crypto/blake2b/blake2bAVX2_amd64.go | 0 .../x/crypto/blake2b/blake2bAVX2_amd64.s | 0 .../x/crypto/blake2b/blake2b_amd64.go | 0 .../x/crypto/blake2b/blake2b_amd64.s | 0 .../x/crypto/blake2b/blake2b_generic.go | 0 .../x/crypto/blake2b/blake2b_ref.go | 0 .../golang.org/x/crypto/blake2b/blake2x.go | 0 .../golang.org/x/crypto/blake2b/register.go | 0 167 files changed, 1127 insertions(+), 514 deletions(-) delete mode 100644 src/disposa.blue/margo/cmd/margo/main.go delete mode 100644 src/disposa.blue/margo/cmdpkg/margo/main.go delete mode 100644 src/disposa.blue/margo/mg/log.go delete mode 100644 src/disposa.blue/margo/sublime/sublime.go rename src/{disposa.blue/margo => margo.sh}/.gitignore (100%) rename src/{disposa.blue/margo => margo.sh}/.travis.yml (66%) rename src/{disposa.blue/margo => margo.sh}/AUTHORS (100%) rename src/{disposa.blue/margo => margo.sh}/Gopkg.lock (61%) rename src/{disposa.blue/margo => margo.sh}/Gopkg.toml (91%) rename src/{disposa.blue/margo => margo.sh}/LICENSE (100%) rename src/{disposa.blue/margo => margo.sh}/README.md (100%) rename src/{disposa.blue/margo => margo.sh}/cmd/margo.sublime/main.go (60%) create mode 100644 src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go create mode 100644 src/margo.sh/cmdpkg/margo/main.go rename src/{disposa.blue/margo => margo.sh}/cmdpkg/margosublime/main-extension.go (100%) rename src/{disposa.blue/margo => margo.sh}/cmdpkg/margosublime/main.go (88%) rename src/{disposa.blue/margo => margo.sh}/extension-example/extension-example.go (98%) rename src/{disposa.blue/margo => margo.sh}/extension-example/extension_test.go (68%) rename src/{disposa.blue/margo => margo.sh}/golang/common.go (99%) rename src/{disposa.blue/margo => margo.sh}/golang/completion.go (98%) rename src/{disposa.blue/margo => margo.sh}/golang/completion_test.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/gocode.go (98%) rename src/{disposa.blue/margo => margo.sh}/golang/gofmt.go (96%) rename src/{disposa.blue/margo => margo.sh}/golang/golang.go (69%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/.gitignore (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/LICENSE (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/README.md (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/autocompletecontext.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/autocompletefile.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/bridge._margo_.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/client.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/config.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/cursorcontext.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/decl.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/declcache.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/formatters.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/generate._margo_.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/gocode.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/os_posix.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/os_windows.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/package.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/package_bin.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/package_text.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/pre_go17.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/ripper.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/rpc.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/scope.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/server.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/type_alias_build_hack_18.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/type_alias_build_hack_19.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/internal/gocode/utils.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/lint.go (99%) rename src/{disposa.blue/margo => margo.sh}/golang/parse.go (98%) rename src/{disposa.blue/margo => margo.sh}/golang/parse_test.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/snippets.go (99%) rename src/{disposa.blue/margo => margo.sh}/golang/syntaxcheck.go (97%) rename src/{disposa.blue/margo => margo.sh}/golang/version.go (100%) rename src/{disposa.blue/margo => margo.sh}/golang/version_test.go (100%) rename src/{disposa.blue/margo => margo.sh}/js/jsonfmt.go (96%) create mode 100644 src/margo.sh/main.go rename src/{disposa.blue/margo => margo.sh}/mg/action.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/agent.go (95%) rename src/{disposa.blue/margo => margo.sh}/mg/agent_test.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/client-actions.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/common.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/completion.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/db.go (100%) create mode 100644 src/margo.sh/mg/doc.go rename src/{disposa.blue/margo => margo.sh}/mg/extension.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/issue.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/issue_test.go (100%) create mode 100644 src/margo.sh/mg/log.go rename src/{disposa.blue/margo => margo.sh}/mg/reducers.go (59%) rename src/{disposa.blue/margo => margo.sh}/mg/state.go (98%) rename src/{disposa.blue/margo => margo.sh}/mg/store.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/tasks.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/tempdir.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/tooltips.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/view.go (100%) rename src/{disposa.blue/margo => margo.sh}/mg/view_test.go (100%) rename src/{disposa.blue/margo => margo.sh}/mgcli/mgcli.go (89%) rename src/{disposa.blue/margo => margo.sh}/misc/pprof/pprofdo/pprofdo-fallback.go (100%) rename src/{disposa.blue/margo => margo.sh}/misc/pprof/pprofdo/pprofdo.go (100%) rename src/{disposa.blue/margo => margo.sh}/misc/pprof/pprofhttp/pprofhttp.go (100%) rename src/{disposa.blue/margo => margo.sh}/sublime/config.go (98%) rename src/{disposa.blue/margo => margo.sh}/sublime/ext.go (69%) create mode 100644 src/margo.sh/sublime/sublime.go rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/LICENSE (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/0doc.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/README.md (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/binc.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/cbor.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/decode.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/encode.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/fast-path.generated.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/fast-path.go.tmpl (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/fast-path.not.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/gen-dec-array.go.tmpl (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/gen-dec-map.go.tmpl (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/gen-enc-chan.go.tmpl (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/gen-helper.generated.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/gen-helper.go.tmpl (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/gen.generated.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/gen.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_makemap_gte_go19.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_makemap_lt_go19.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_gte_go110.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_lt_go110.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_unsupported_lt_go14.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go15.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go16.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_vendor_gte_go17.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/goversion_vendor_lt_go15.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/helper.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/helper_internal.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/helper_not_unsafe.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/helper_unsafe.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/json.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/mammoth-test.go.tmpl (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/mammoth2-test.go.tmpl (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/msgpack.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/rpc.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/simple.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/test-cbor-goldens.json (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/test.py (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/ugorji/go/codec/xml.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/.flake8 (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/.gitignore (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/.travis.yml (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/CHANGELOG.md (100%) create mode 100644 src/margo.sh/vendor/github.com/urfave/cli/CODE_OF_CONDUCT.md create mode 100644 src/margo.sh/vendor/github.com/urfave/cli/CONTRIBUTING.md rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/LICENSE (100%) create mode 100644 src/margo.sh/vendor/github.com/urfave/cli/MAINTAINERS.md rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/README.md (88%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/app.go (94%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/appveyor.yml (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/category.go (95%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/cli.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/command.go (75%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/context.go (92%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/errors.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/flag-types.json (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/flag.go (70%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/flag_generated.go (95%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/funcs.go (64%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/generate-flag-types (99%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/help.go (97%) rename src/{disposa.blue/margo => margo.sh}/vendor/github.com/urfave/cli/runtests (100%) create mode 100644 src/margo.sh/vendor/github.com/urfave/cli/sort.go rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/AUTHORS (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/CONTRIBUTORS (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/LICENSE (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/PATENTS (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/blake2b/blake2b.go (76%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/blake2b/blake2x.go (100%) rename src/{disposa.blue/margo => margo.sh}/vendor/golang.org/x/crypto/blake2b/register.go (100%) diff --git a/gosubl/margo.py b/gosubl/margo.py index 1d5e0d71..365fc6b0 100644 --- a/gosubl/margo.py +++ b/gosubl/margo.py @@ -209,7 +209,7 @@ def on_close(self, view): self.send(view=view, action=actions.ViewClosed) def example_extension_file(self): - return gs.dist_path('src/disposa.blue/margo/extension-example/extension-example.go') + return gs.dist_path('src/margo.sh/extension-example/extension-example.go') def extension_file(self, install=False): src_dir = gs.user_path('src', 'margo') diff --git a/gosubl/margo_agent.py b/gosubl/margo_agent.py index 53e9e0a3..7212e21b 100644 --- a/gosubl/margo_agent.py +++ b/gosubl/margo_agent.py @@ -90,7 +90,8 @@ def _start_proc(self): gs_gopath = sh.psep.join((gs.user_path(), gs.dist_path())) gs_gobin = gs.dist_path('bin') - install_cmd = ['go', 'install', '-v', 'disposa.blue/margo/cmd/margo'] + mg_exe = 'margo.sh' + install_cmd = ['go', 'install', '-v', mg_exe] cmd = sh.Command(install_cmd) cmd.env = { 'GOPATH': gs_gopath, @@ -102,8 +103,8 @@ def _start_proc(self): self.out.println('%s:\n%s' % (install_cmd, v)) mg_cmd = [ - sh.which('margo', m={'PATH': gs_gobin}) or 'margo', - 'sublime', '-codec', ipc_codec, + sh.which(mg_exe, m={'PATH': gs_gobin}) or mg_exe, + 'start', 'margo.sublime', '-codec', ipc_codec, ] self.out.println(mg_cmd) cmd = sh.Command(mg_cmd) diff --git a/src/disposa.blue/margo/cmd/margo/main.go b/src/disposa.blue/margo/cmd/margo/main.go deleted file mode 100644 index a93d4f8c..00000000 --- a/src/disposa.blue/margo/cmd/margo/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "disposa.blue/margo/cmdpkg/margo" -) - -func main() { - margo.Main() -} diff --git a/src/disposa.blue/margo/cmdpkg/margo/main.go b/src/disposa.blue/margo/cmdpkg/margo/main.go deleted file mode 100644 index f9661961..00000000 --- a/src/disposa.blue/margo/cmdpkg/margo/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package margo - -import ( - "disposa.blue/margo/mgcli" - "disposa.blue/margo/sublime" - "github.com/urfave/cli" -) - -func Main() { - app := mgcli.NewApp() - app.Commands = []cli.Command{ - sublime.Command, - } - app.RunAndExitOnError() -} diff --git a/src/disposa.blue/margo/mg/log.go b/src/disposa.blue/margo/mg/log.go deleted file mode 100644 index dd9e7b23..00000000 --- a/src/disposa.blue/margo/mg/log.go +++ /dev/null @@ -1,10 +0,0 @@ -package mg - -import ( - "log" -) - -type Logger struct { - *log.Logger - Dbg *log.Logger -} diff --git a/src/disposa.blue/margo/sublime/sublime.go b/src/disposa.blue/margo/sublime/sublime.go deleted file mode 100644 index 46afae4b..00000000 --- a/src/disposa.blue/margo/sublime/sublime.go +++ /dev/null @@ -1,105 +0,0 @@ -package sublime - -import ( - "disposa.blue/margo/mgcli" - "fmt" - "github.com/urfave/cli" - "go/build" - "os" - "os/exec" - "strings" -) - -var ( - Command = cli.Command{ - Name: "sublime", - Aliases: []string{"subl"}, - Usage: "", - Description: "", - SkipFlagParsing: true, - SkipArgReorder: true, - Action: mgcli.Action(mainAction), - } -) - -type cmdHelper struct { - name string - args []string - outToErr bool - env []string -} - -func (c cmdHelper) run() error { - cmd := exec.Command(c.name, c.args...) - cmd.Stdin = os.Stdin - if c.outToErr { - cmd.Stdout = os.Stderr - } else { - cmd.Stdout = os.Stdout - } - cmd.Stderr = os.Stderr - cmd.Env = c.env - - fmt.Fprintf(os.Stderr, "run%q\n", append([]string{c.name}, c.args...)) - return cmd.Run() -} - -func mainAction(c *cli.Context) error { - args := c.Args() - tags := "margo" - if extensionPkgExists() { - tags = "margo margo_extension" - } - var env []string - if err := goInstallAgent(os.Getenv("MARGO_SUBLIME_GOPATH"), tags); err != nil { - env = append(env, "MARGO_SUBLIME_INSTALL_FAILED=margo install failed. check console for errors") - fmt.Fprintln(os.Stderr, "cannot install margo.sublime:", err) - } - name := "margo.sublime" - if exe, err := exec.LookPath(name); err == nil { - name = exe - } - return cmdHelper{name: name, args: args, env: env}.run() -} - -func goInstallAgent(gp string, tags string) error { - var env []string - if gp != "" { - env = make([]string, 0, len(os.Environ())+1) - // I don't remember the rules about duplicate env vars... - for _, s := range os.Environ() { - if !strings.HasPrefix(s, "GOPATH=") { - env = append(env, s) - } - } - env = append(env, "GOPATH="+gp) - } - - cmdpath := "disposa.blue/margo/cmd/margo.sublime" - if s := os.Getenv("MARGO_SUBLIME_CMDPATH"); s != "" { - cmdpath = s - } - - args := []string{"install", "-v", "-tags=" + tags} - if os.Getenv("MARGO_INSTALL_FLAGS_RACE") == "1" { - args = append(args, "-race") - } - for _, tag := range build.Default.ReleaseTags { - if tag == "go1.10" { - args = append(args, "-i") - break - } - } - args = append(args, cmdpath) - return cmdHelper{ - name: "go", - args: args, - outToErr: true, - env: env, - }.run() -} - -func extensionPkgExists() bool { - _, err := build.Import("margo", "", 0) - return err == nil -} diff --git a/src/disposa.blue/margo/.gitignore b/src/margo.sh/.gitignore similarity index 100% rename from src/disposa.blue/margo/.gitignore rename to src/margo.sh/.gitignore diff --git a/src/disposa.blue/margo/.travis.yml b/src/margo.sh/.travis.yml similarity index 66% rename from src/disposa.blue/margo/.travis.yml rename to src/margo.sh/.travis.yml index 71c75fc7..06fd08a3 100644 --- a/src/disposa.blue/margo/.travis.yml +++ b/src/margo.sh/.travis.yml @@ -1,4 +1,4 @@ -go_import_path: disposa.blue/margo +go_import_path: margo.sh language: go go: - 1.9 diff --git a/src/disposa.blue/margo/AUTHORS b/src/margo.sh/AUTHORS similarity index 100% rename from src/disposa.blue/margo/AUTHORS rename to src/margo.sh/AUTHORS diff --git a/src/disposa.blue/margo/Gopkg.lock b/src/margo.sh/Gopkg.lock similarity index 61% rename from src/disposa.blue/margo/Gopkg.lock rename to src/margo.sh/Gopkg.lock index 3a538bbd..487396fc 100644 --- a/src/disposa.blue/margo/Gopkg.lock +++ b/src/margo.sh/Gopkg.lock @@ -5,23 +5,23 @@ branch = "master" name = "github.com/ugorji/go" packages = ["codec"] - revision = "16f09ef744fd4227190f626f14cfdefb14362b3b" + revision = "02537d3a3e32ef636a53519265d211bd208ca488" [[projects]] + branch = "master" name = "github.com/urfave/cli" packages = ["."] - revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" - version = "v1.20.0" + revision = "8e01ec4cd3e2d84ab2fe90d8210528ffbb06d8ff" [[projects]] branch = "master" name = "golang.org/x/crypto" packages = ["blake2b"] - revision = "91a49db82a88618983a78a06c1cbd4e00ab749ab" + revision = "12892e8c234f4fe6f6803f052061de9057903bb2" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "abcbbea5d46799cef82db8ef20f2f68d7d9580d20996b07df2f68d66cad13a49" + inputs-digest = "a7d8a00d2d704fb62eed79ee67536c295268edae7c67b316af3af70397acad95" solver-name = "gps-cdcl" solver-version = 1 diff --git a/src/disposa.blue/margo/Gopkg.toml b/src/margo.sh/Gopkg.toml similarity index 91% rename from src/disposa.blue/margo/Gopkg.toml rename to src/margo.sh/Gopkg.toml index 46f81a2c..48f5c2eb 100644 --- a/src/disposa.blue/margo/Gopkg.toml +++ b/src/margo.sh/Gopkg.toml @@ -4,7 +4,7 @@ [[constraint]] name = "github.com/urfave/cli" - version = "1.20.0" + branch = "master" [[constraint]] branch = "master" diff --git a/src/disposa.blue/margo/LICENSE b/src/margo.sh/LICENSE similarity index 100% rename from src/disposa.blue/margo/LICENSE rename to src/margo.sh/LICENSE diff --git a/src/disposa.blue/margo/README.md b/src/margo.sh/README.md similarity index 100% rename from src/disposa.blue/margo/README.md rename to src/margo.sh/README.md diff --git a/src/disposa.blue/margo/cmd/margo.sublime/main.go b/src/margo.sh/cmd/margo.sublime/main.go similarity index 60% rename from src/disposa.blue/margo/cmd/margo.sublime/main.go rename to src/margo.sh/cmd/margo.sublime/main.go index e7947790..bb1e34f0 100644 --- a/src/disposa.blue/margo/cmd/margo.sublime/main.go +++ b/src/margo.sh/cmd/margo.sublime/main.go @@ -1,7 +1,7 @@ package main import ( - "disposa.blue/margo/cmdpkg/margosublime" + "margo.sh/cmdpkg/margosublime" ) func main() { diff --git a/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go b/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go new file mode 100644 index 00000000..fc45ba38 --- /dev/null +++ b/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go @@ -0,0 +1,64 @@ +package cmdrunner + +import ( + "fmt" + "os" + "os/exec" + "strconv" + "strings" +) + +type Cmd struct { + Name string + Args []string + Env map[string]string + OutToErr bool +} + +func (c Cmd) Run() error { + cmd := exec.Command(c.Name, c.Args...) + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + if c.OutToErr { + cmd.Stdout = cmd.Stderr + } else { + cmd.Stdout = os.Stdout + } + + if len(c.Env) != 0 { + environ := os.Environ() + cmd.Env = make([]string, 0, len(environ)+1) + // I don't remember the rules about duplicate env vars... + for _, s := range os.Environ() { + k := strings.Split(s, "=")[0] + if _, exists := c.Env[k]; !exists { + cmd.Env = append(cmd.Env, s) + } + } + for k, v := range c.Env { + cmd.Env = append(cmd.Env, k+"="+v) + } + } + + a := append([]string{c.Name}, c.Args...) + for i, s := range a { + a[i] = quoteFlag(s) + } + fmt.Fprintf(os.Stderr, "``` %s ```\n", strings.Join(a, " ")) + + return cmd.Run() +} + +func quoteFlag(s string) string { + eqPos := strings.Index(s, "=") + switch { + case s == "": + return `""` + case !strings.Contains(s, " "): + return s + case strings.HasPrefix(s, "-") && eqPos > 0: + return s[:eqPos+1] + strconv.Quote(s[eqPos+1:]) + default: + return strconv.Quote(s) + } +} diff --git a/src/margo.sh/cmdpkg/margo/main.go b/src/margo.sh/cmdpkg/margo/main.go new file mode 100644 index 00000000..01cf2a08 --- /dev/null +++ b/src/margo.sh/cmdpkg/margo/main.go @@ -0,0 +1,95 @@ +package margo + +import ( + "flag" + "fmt" + "github.com/urfave/cli" + "margo.sh/mgcli" + "margo.sh/sublime" + "os" +) + +var ( + cmdList = []mgcli.Commands{ + sublime.Commands, + } + + cmdNames []string + + cmdMap map[string]mgcli.Commands + + buildCmd = cli.Command{ + Name: "build", + Description: "build the specified agent (see COMMANDS)", + } + + runCmd = cli.Command{ + Name: "run", + Description: "run the specified agent (see COMMANDS)", + } + + startCmd = cli.Command{ + Name: "start", + Description: "`build` and `run` the specified agent (see COMMANDS)", + } +) + +func init() { + cmdMap = map[string]mgcli.Commands{} + buildNames := []string{} + runNames := []string{} + for _, mc := range cmdList { + cmdNames = append(cmdNames, mc.Name) + cmdMap[mc.Name] = mc + if mc.Build != nil { + buildNames = append(buildNames, mc.Name) + appendSubCmd(&buildCmd, mc, *mc.Build) + } + if mc.Run != nil { + runNames = append(runNames, mc.Name) + appendSubCmd(&runCmd, mc, *mc.Run) + } + appendSubCmd(&startCmd, mc, cli.Command{ + Action: startAction, + SkipFlagParsing: true, + SkipArgReorder: true, + }) + } +} + +func Main() { + app := mgcli.NewApp() + app.Commands = []cli.Command{buildCmd, runCmd, startCmd} + app.RunAndExitOnError() +} + +func appendSubCmd(cmd *cli.Command, cmds mgcli.Commands, subCmd cli.Command) { + if subCmd.Name == "" { + subCmd.Name = cmds.Name + } + cmd.Subcommands = append(cmd.Subcommands, subCmd) +} + +func startAction(cx *cli.Context) error { + mc := cmdMap[cx.Command.Name] + app := &mgcli.NewApp().App + app.Name = mc.Name + app.ExitErrHandler = func(_ *cli.Context, _ error) {} + newCtx := func(args []string) *cli.Context { + flags := flag.NewFlagSet(mc.Name, 0) + flags.Usage = func() {} + flags.Parse(append([]string{mc.Name}, args...)) + return cli.NewContext(app, flags, cx) + } + if mc.Build != nil { + err := mc.Build.Run(newCtx(nil)) + if err != nil { + e := fmt.Sprintf("%s build failed: %s", mc.Name, err) + os.Setenv("MARGO_BUILD_ERROR", e) + } + } + if mc.Run != nil { + return mc.Run.Run(newCtx(cx.Args())) + } + return nil +} diff --git a/src/disposa.blue/margo/cmdpkg/margosublime/main-extension.go b/src/margo.sh/cmdpkg/margosublime/main-extension.go similarity index 100% rename from src/disposa.blue/margo/cmdpkg/margosublime/main-extension.go rename to src/margo.sh/cmdpkg/margosublime/main-extension.go diff --git a/src/disposa.blue/margo/cmdpkg/margosublime/main.go b/src/margo.sh/cmdpkg/margosublime/main.go similarity index 88% rename from src/disposa.blue/margo/cmdpkg/margosublime/main.go rename to src/margo.sh/cmdpkg/margosublime/main.go index 4c4e13f6..4c0fb974 100644 --- a/src/disposa.blue/margo/cmdpkg/margosublime/main.go +++ b/src/margo.sh/cmdpkg/margosublime/main.go @@ -1,11 +1,11 @@ package margosublime import ( - "disposa.blue/margo/mg" - "disposa.blue/margo/mgcli" - "disposa.blue/margo/sublime" "fmt" "github.com/urfave/cli" + "margo.sh/mg" + "margo.sh/mgcli" + "margo.sh/sublime" ) var ( @@ -14,7 +14,7 @@ var ( ) func Main() { - cfg := mg.AgentConfig{} + cfg := mg.AgentConfig{AgentName: sublime.AgentName} app := mgcli.NewApp() app.Flags = []cli.Flag{ cli.StringFlag{ diff --git a/src/disposa.blue/margo/extension-example/extension-example.go b/src/margo.sh/extension-example/extension-example.go similarity index 98% rename from src/disposa.blue/margo/extension-example/extension-example.go rename to src/margo.sh/extension-example/extension-example.go index 5c07e51e..08f0b175 100644 --- a/src/disposa.blue/margo/extension-example/extension-example.go +++ b/src/margo.sh/extension-example/extension-example.go @@ -1,8 +1,8 @@ package margo import ( - "disposa.blue/margo/golang" - "disposa.blue/margo/mg" + "margo.sh/golang" + "margo.sh/mg" "time" ) diff --git a/src/disposa.blue/margo/extension-example/extension_test.go b/src/margo.sh/extension-example/extension_test.go similarity index 68% rename from src/disposa.blue/margo/extension-example/extension_test.go rename to src/margo.sh/extension-example/extension_test.go index 7e772037..837a3131 100644 --- a/src/disposa.blue/margo/extension-example/extension_test.go +++ b/src/margo.sh/extension-example/extension_test.go @@ -1,7 +1,7 @@ package margo import ( - "disposa.blue/margo/mg" + "margo.sh/mg" ) var _ mg.MargoFunc = Margo diff --git a/src/disposa.blue/margo/golang/common.go b/src/margo.sh/golang/common.go similarity index 99% rename from src/disposa.blue/margo/golang/common.go rename to src/margo.sh/golang/common.go index fd889529..bff089b6 100644 --- a/src/disposa.blue/margo/golang/common.go +++ b/src/margo.sh/golang/common.go @@ -1,7 +1,7 @@ package golang import ( - "disposa.blue/margo/mg" + "margo.sh/mg" "go/ast" "go/build" "go/token" diff --git a/src/disposa.blue/margo/golang/completion.go b/src/margo.sh/golang/completion.go similarity index 98% rename from src/disposa.blue/margo/golang/completion.go rename to src/margo.sh/golang/completion.go index 982cc91c..106ff412 100644 --- a/src/disposa.blue/margo/golang/completion.go +++ b/src/margo.sh/golang/completion.go @@ -1,7 +1,7 @@ package golang import ( - "disposa.blue/margo/mg" + "margo.sh/mg" "go/ast" "go/token" "strings" diff --git a/src/disposa.blue/margo/golang/completion_test.go b/src/margo.sh/golang/completion_test.go similarity index 100% rename from src/disposa.blue/margo/golang/completion_test.go rename to src/margo.sh/golang/completion_test.go diff --git a/src/disposa.blue/margo/golang/gocode.go b/src/margo.sh/golang/gocode.go similarity index 98% rename from src/disposa.blue/margo/golang/gocode.go rename to src/margo.sh/golang/gocode.go index 32f3877a..7e6a86b6 100644 --- a/src/disposa.blue/margo/golang/gocode.go +++ b/src/margo.sh/golang/gocode.go @@ -2,9 +2,9 @@ package golang import ( "bytes" - "disposa.blue/margo/golang/internal/gocode" - "disposa.blue/margo/mg" - "disposa.blue/margo/sublime" + "margo.sh/golang/internal/gocode" + "margo.sh/mg" + "margo.sh/sublime" "fmt" "go/ast" "go/build" diff --git a/src/disposa.blue/margo/golang/gofmt.go b/src/margo.sh/golang/gofmt.go similarity index 96% rename from src/disposa.blue/margo/golang/gofmt.go rename to src/margo.sh/golang/gofmt.go index f30b1496..bd4ae5e7 100644 --- a/src/disposa.blue/margo/golang/gofmt.go +++ b/src/margo.sh/golang/gofmt.go @@ -6,8 +6,8 @@ import ( "go/format" "os/exec" - "disposa.blue/margo/mg" - "disposa.blue/margo/sublime" + "margo.sh/mg" + "margo.sh/sublime" ) var ( diff --git a/src/disposa.blue/margo/golang/golang.go b/src/margo.sh/golang/golang.go similarity index 69% rename from src/disposa.blue/margo/golang/golang.go rename to src/margo.sh/golang/golang.go index 35245caa..600ee48f 100644 --- a/src/disposa.blue/margo/golang/golang.go +++ b/src/margo.sh/golang/golang.go @@ -1,7 +1,7 @@ package golang import ( - "disposa.blue/margo/mg" + "margo.sh/mg" ) var Reducers = []mg.Reducer{} diff --git a/src/disposa.blue/margo/golang/internal/gocode/.gitignore b/src/margo.sh/golang/internal/gocode/.gitignore similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/.gitignore rename to src/margo.sh/golang/internal/gocode/.gitignore diff --git a/src/disposa.blue/margo/golang/internal/gocode/LICENSE b/src/margo.sh/golang/internal/gocode/LICENSE similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/LICENSE rename to src/margo.sh/golang/internal/gocode/LICENSE diff --git a/src/disposa.blue/margo/golang/internal/gocode/README.md b/src/margo.sh/golang/internal/gocode/README.md similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/README.md rename to src/margo.sh/golang/internal/gocode/README.md diff --git a/src/disposa.blue/margo/golang/internal/gocode/autocompletecontext.go b/src/margo.sh/golang/internal/gocode/autocompletecontext.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/autocompletecontext.go rename to src/margo.sh/golang/internal/gocode/autocompletecontext.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/autocompletefile.go b/src/margo.sh/golang/internal/gocode/autocompletefile.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/autocompletefile.go rename to src/margo.sh/golang/internal/gocode/autocompletefile.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/bridge._margo_.go b/src/margo.sh/golang/internal/gocode/bridge._margo_.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/bridge._margo_.go rename to src/margo.sh/golang/internal/gocode/bridge._margo_.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/client.go b/src/margo.sh/golang/internal/gocode/client.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/client.go rename to src/margo.sh/golang/internal/gocode/client.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/config.go b/src/margo.sh/golang/internal/gocode/config.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/config.go rename to src/margo.sh/golang/internal/gocode/config.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/cursorcontext.go b/src/margo.sh/golang/internal/gocode/cursorcontext.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/cursorcontext.go rename to src/margo.sh/golang/internal/gocode/cursorcontext.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/decl.go b/src/margo.sh/golang/internal/gocode/decl.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/decl.go rename to src/margo.sh/golang/internal/gocode/decl.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/declcache.go b/src/margo.sh/golang/internal/gocode/declcache.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/declcache.go rename to src/margo.sh/golang/internal/gocode/declcache.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/formatters.go b/src/margo.sh/golang/internal/gocode/formatters.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/formatters.go rename to src/margo.sh/golang/internal/gocode/formatters.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/generate._margo_.go b/src/margo.sh/golang/internal/gocode/generate._margo_.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/generate._margo_.go rename to src/margo.sh/golang/internal/gocode/generate._margo_.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/gocode.go b/src/margo.sh/golang/internal/gocode/gocode.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/gocode.go rename to src/margo.sh/golang/internal/gocode/gocode.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/os_posix.go b/src/margo.sh/golang/internal/gocode/os_posix.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/os_posix.go rename to src/margo.sh/golang/internal/gocode/os_posix.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/os_windows.go b/src/margo.sh/golang/internal/gocode/os_windows.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/os_windows.go rename to src/margo.sh/golang/internal/gocode/os_windows.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/package.go b/src/margo.sh/golang/internal/gocode/package.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/package.go rename to src/margo.sh/golang/internal/gocode/package.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/package_bin.go b/src/margo.sh/golang/internal/gocode/package_bin.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/package_bin.go rename to src/margo.sh/golang/internal/gocode/package_bin.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/package_text.go b/src/margo.sh/golang/internal/gocode/package_text.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/package_text.go rename to src/margo.sh/golang/internal/gocode/package_text.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/pre_go17.go b/src/margo.sh/golang/internal/gocode/pre_go17.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/pre_go17.go rename to src/margo.sh/golang/internal/gocode/pre_go17.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/ripper.go b/src/margo.sh/golang/internal/gocode/ripper.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/ripper.go rename to src/margo.sh/golang/internal/gocode/ripper.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/rpc.go b/src/margo.sh/golang/internal/gocode/rpc.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/rpc.go rename to src/margo.sh/golang/internal/gocode/rpc.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/scope.go b/src/margo.sh/golang/internal/gocode/scope.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/scope.go rename to src/margo.sh/golang/internal/gocode/scope.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/server.go b/src/margo.sh/golang/internal/gocode/server.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/server.go rename to src/margo.sh/golang/internal/gocode/server.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/type_alias_build_hack_18.go b/src/margo.sh/golang/internal/gocode/type_alias_build_hack_18.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/type_alias_build_hack_18.go rename to src/margo.sh/golang/internal/gocode/type_alias_build_hack_18.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/type_alias_build_hack_19.go b/src/margo.sh/golang/internal/gocode/type_alias_build_hack_19.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/type_alias_build_hack_19.go rename to src/margo.sh/golang/internal/gocode/type_alias_build_hack_19.go diff --git a/src/disposa.blue/margo/golang/internal/gocode/utils.go b/src/margo.sh/golang/internal/gocode/utils.go similarity index 100% rename from src/disposa.blue/margo/golang/internal/gocode/utils.go rename to src/margo.sh/golang/internal/gocode/utils.go diff --git a/src/disposa.blue/margo/golang/lint.go b/src/margo.sh/golang/lint.go similarity index 99% rename from src/disposa.blue/margo/golang/lint.go rename to src/margo.sh/golang/lint.go index 498fc42d..313ce714 100644 --- a/src/disposa.blue/margo/golang/lint.go +++ b/src/margo.sh/golang/lint.go @@ -2,7 +2,7 @@ package golang import ( "bytes" - "disposa.blue/margo/mg" + "margo.sh/mg" "os" "os/exec" "regexp" diff --git a/src/disposa.blue/margo/golang/parse.go b/src/margo.sh/golang/parse.go similarity index 98% rename from src/disposa.blue/margo/golang/parse.go rename to src/margo.sh/golang/parse.go index 29257cbf..25c55085 100644 --- a/src/disposa.blue/margo/golang/parse.go +++ b/src/margo.sh/golang/parse.go @@ -1,7 +1,7 @@ package golang import ( - "disposa.blue/margo/mg" + "margo.sh/mg" "go/ast" "go/parser" "go/scanner" diff --git a/src/disposa.blue/margo/golang/parse_test.go b/src/margo.sh/golang/parse_test.go similarity index 100% rename from src/disposa.blue/margo/golang/parse_test.go rename to src/margo.sh/golang/parse_test.go diff --git a/src/disposa.blue/margo/golang/snippets.go b/src/margo.sh/golang/snippets.go similarity index 99% rename from src/disposa.blue/margo/golang/snippets.go rename to src/margo.sh/golang/snippets.go index ba8b5354..25bb6210 100644 --- a/src/disposa.blue/margo/golang/snippets.go +++ b/src/margo.sh/golang/snippets.go @@ -1,7 +1,7 @@ package golang import ( - "disposa.blue/margo/mg" + "margo.sh/mg" "go/ast" "unicode" "unicode/utf8" diff --git a/src/disposa.blue/margo/golang/syntaxcheck.go b/src/margo.sh/golang/syntaxcheck.go similarity index 97% rename from src/disposa.blue/margo/golang/syntaxcheck.go rename to src/margo.sh/golang/syntaxcheck.go index 137a4e48..9394ce83 100644 --- a/src/disposa.blue/margo/golang/syntaxcheck.go +++ b/src/margo.sh/golang/syntaxcheck.go @@ -1,7 +1,7 @@ package golang import ( - "disposa.blue/margo/mg" + "margo.sh/mg" "go/scanner" ) diff --git a/src/disposa.blue/margo/golang/version.go b/src/margo.sh/golang/version.go similarity index 100% rename from src/disposa.blue/margo/golang/version.go rename to src/margo.sh/golang/version.go diff --git a/src/disposa.blue/margo/golang/version_test.go b/src/margo.sh/golang/version_test.go similarity index 100% rename from src/disposa.blue/margo/golang/version_test.go rename to src/margo.sh/golang/version_test.go diff --git a/src/disposa.blue/margo/js/jsonfmt.go b/src/margo.sh/js/jsonfmt.go similarity index 96% rename from src/disposa.blue/margo/js/jsonfmt.go rename to src/margo.sh/js/jsonfmt.go index 7e160058..e7a2f85f 100644 --- a/src/disposa.blue/margo/js/jsonfmt.go +++ b/src/margo.sh/js/jsonfmt.go @@ -1,7 +1,7 @@ package js import ( - "disposa.blue/margo/mg" + "margo.sh/mg" "encoding/json" ) diff --git a/src/margo.sh/main.go b/src/margo.sh/main.go new file mode 100644 index 00000000..645d231d --- /dev/null +++ b/src/margo.sh/main.go @@ -0,0 +1,9 @@ +package main // import "margo.sh" + +import ( + "margo.sh/cmdpkg/margo" +) + +func main() { + margo.Main() +} diff --git a/src/disposa.blue/margo/mg/action.go b/src/margo.sh/mg/action.go similarity index 100% rename from src/disposa.blue/margo/mg/action.go rename to src/margo.sh/mg/action.go diff --git a/src/disposa.blue/margo/mg/agent.go b/src/margo.sh/mg/agent.go similarity index 95% rename from src/disposa.blue/margo/mg/agent.go rename to src/margo.sh/mg/agent.go index 2120dc40..410dd107 100644 --- a/src/disposa.blue/margo/mg/agent.go +++ b/src/margo.sh/mg/agent.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/ugorji/go/codec" "io" - "log" "os" "sort" "strings" @@ -51,6 +50,9 @@ var ( ) type AgentConfig struct { + // the name of the agent as used in the command `margo.sh [start...] $AgentName` + AgentName string + // Codec is the name of the codec to use for IPC // Valid values are json, cbor or msgpack // Default: json @@ -112,6 +114,8 @@ func (rs agentRes) finalize() interface{} { } type Agent struct { + Name string + Log *Logger Store *Store @@ -185,6 +189,7 @@ func (ag *Agent) shutdownIPC() { func NewAgent(cfg AgentConfig) (*Agent, error) { ag := &Agent{ + Name: cfg.AgentName, stdin: cfg.Stdin, stdout: cfg.Stdout, stderr: cfg.Stderr, @@ -202,16 +207,13 @@ func NewAgent(cfg AgentConfig) (*Agent, error) { ag.stdin = &LockedReadCloser{ReadCloser: ag.stdin} ag.stdout = &LockedWriteCloser{WriteCloser: ag.stdout} ag.stderr = &LockedWriteCloser{WriteCloser: ag.stderr} - ag.Log = &Logger{ - Logger: log.New(ag.stderr, "", log.Lshortfile), - Dbg: log.New(ag.stderr, "DBG: ", log.Lshortfile), - } + ag.Log = NewLogger(ag.stderr) ag.Store = newStore(ag, ag.listener). Before(defaultReducers.before...). Use(defaultReducers.use...). After(defaultReducers.after...) - if e := os.Getenv("MARGO_SUBLIME_INSTALL_FAILED"); e != "" { + if e := os.Getenv("MARGO_BUILD_ERROR"); e != "" { ag.Store.Use(Reduce(func(mx *Ctx) *State { return mx.AddStatus(e) })) diff --git a/src/disposa.blue/margo/mg/agent_test.go b/src/margo.sh/mg/agent_test.go similarity index 100% rename from src/disposa.blue/margo/mg/agent_test.go rename to src/margo.sh/mg/agent_test.go diff --git a/src/disposa.blue/margo/mg/client-actions.go b/src/margo.sh/mg/client-actions.go similarity index 100% rename from src/disposa.blue/margo/mg/client-actions.go rename to src/margo.sh/mg/client-actions.go diff --git a/src/disposa.blue/margo/mg/common.go b/src/margo.sh/mg/common.go similarity index 100% rename from src/disposa.blue/margo/mg/common.go rename to src/margo.sh/mg/common.go diff --git a/src/disposa.blue/margo/mg/completion.go b/src/margo.sh/mg/completion.go similarity index 100% rename from src/disposa.blue/margo/mg/completion.go rename to src/margo.sh/mg/completion.go diff --git a/src/disposa.blue/margo/mg/db.go b/src/margo.sh/mg/db.go similarity index 100% rename from src/disposa.blue/margo/mg/db.go rename to src/margo.sh/mg/db.go diff --git a/src/margo.sh/mg/doc.go b/src/margo.sh/mg/doc.go new file mode 100644 index 00000000..4c896965 --- /dev/null +++ b/src/margo.sh/mg/doc.go @@ -0,0 +1 @@ +package mg // import "margo.sh/mg" diff --git a/src/disposa.blue/margo/mg/extension.go b/src/margo.sh/mg/extension.go similarity index 100% rename from src/disposa.blue/margo/mg/extension.go rename to src/margo.sh/mg/extension.go diff --git a/src/disposa.blue/margo/mg/issue.go b/src/margo.sh/mg/issue.go similarity index 100% rename from src/disposa.blue/margo/mg/issue.go rename to src/margo.sh/mg/issue.go diff --git a/src/disposa.blue/margo/mg/issue_test.go b/src/margo.sh/mg/issue_test.go similarity index 100% rename from src/disposa.blue/margo/mg/issue_test.go rename to src/margo.sh/mg/issue_test.go diff --git a/src/margo.sh/mg/log.go b/src/margo.sh/mg/log.go new file mode 100644 index 00000000..2e7edb44 --- /dev/null +++ b/src/margo.sh/mg/log.go @@ -0,0 +1,18 @@ +package mg + +import ( + "io" + "log" +) + +type Logger struct { + *log.Logger + Dbg *log.Logger +} + +func NewLogger(w io.Writer) *Logger { + return &Logger{ + Logger: log.New(w, "", log.Lshortfile), + Dbg: log.New(w, "DBG: ", log.Lshortfile), + } +} diff --git a/src/disposa.blue/margo/mg/reducers.go b/src/margo.sh/mg/reducers.go similarity index 59% rename from src/disposa.blue/margo/mg/reducers.go rename to src/margo.sh/mg/reducers.go index aad63333..558854af 100644 --- a/src/disposa.blue/margo/mg/reducers.go +++ b/src/margo.sh/mg/reducers.go @@ -12,7 +12,7 @@ var ( before, use, after []Reducer }{ before: []Reducer{ - restartSupport{}, + &restartSupport{}, }, after: []Reducer{ issueSupport{}, @@ -20,26 +20,30 @@ var ( } ) -type restartSupport struct{} +type rsBuildRes struct { + ActionType + issues IssueSet +} + +type restartSupport struct { + issues IssueSet +} -func (r restartSupport) Reduce(mx *Ctx) *State { - switch mx.Action.(type) { +func (r *restartSupport) Reduce(mx *Ctx) *State { + st := mx.State + switch act := mx.Action.(type) { case ViewSaved: - return r.viewSaved(mx) + go r.prepRestart(mx) case Restart: mx.Log.Printf("%T action dispatched\n", mx.Action) - return mx.addClientActions(clientRestart) + st = mx.addClientActions(clientRestart) case Shutdown: mx.Log.Printf("%T action dispatched\n", mx.Action) - return mx.addClientActions(clientShutdown) - default: + st = mx.addClientActions(clientShutdown) + case rsBuildRes: + r.issues = act.issues } - return mx.State -} - -func (r restartSupport) viewSaved(mx *Ctx) *State { - go r.prepRestart(mx) - return mx.State + return st.AddIssues(r.issues...) } func (_ restartSupport) prepRestart(mx *Ctx) { @@ -53,7 +57,7 @@ func (_ restartSupport) prepRestart(mx *Ctx) { if i := strings.LastIndex(dir, "/src/"); i >= 0 { imp = dir[i+5:] } - if imp != "margo" && !strings.HasPrefix(imp+"/", "disposa.blue/margo/") { + if imp != "margo" && !strings.HasPrefix(imp+"/", "margo.sh/") { return } @@ -64,15 +68,26 @@ func (_ restartSupport) prepRestart(mx *Ctx) { defer mx.Begin(Task{Title: "prepping margo restart"}).Done() - cmd := exec.Command("go", "test") + cmd := exec.Command("margo.sh", "build", mx.AgentName()) cmd.Dir = mx.View.Dir() cmd.Env = mx.Env.Environ() out, err := cmd.CombinedOutput() + + iw := &IssueWriter{ + Dir: mx.View.Dir(), + Patterns: CommonPatterns, + Base: Issue{Label: "Mg/RestartSupport"}, + } + iw.Write(out) + iw.Flush() + res := rsBuildRes{issues: iw.Issues()} + msg := "telling margo to restart after " + mx.View.Filename() + " was saved" - if err == nil { + if err == nil && len(res.issues) == 0 { mx.Log.Println(msg) mx.Store.Dispatch(Restart{}) } else { mx.Log.Printf("not %s: go test failed: %s\n%s\n", msg, err, out) + mx.Store.Dispatch(res) } } diff --git a/src/disposa.blue/margo/mg/state.go b/src/margo.sh/mg/state.go similarity index 98% rename from src/disposa.blue/margo/mg/state.go rename to src/margo.sh/mg/state.go index c42dc12d..075abeda 100644 --- a/src/disposa.blue/margo/mg/state.go +++ b/src/margo.sh/mg/state.go @@ -2,10 +2,10 @@ package mg import ( "context" - "disposa.blue/margo/misc/pprof/pprofdo" "fmt" "github.com/ugorji/go/codec" "go/build" + "margo.sh/misc/pprof/pprofdo" "os" "path/filepath" "reflect" @@ -57,6 +57,10 @@ func (mx *Ctx) Value(k interface{}) interface{} { return nil } +func (mx *Ctx) AgentName() string { + return mx.Store.ag.Name +} + func newCtx(ag *Agent, st *State, act Action, sto *Store) (mx *Ctx, done chan struct{}) { if st == nil { panic("newCtx: state must not be nil") diff --git a/src/disposa.blue/margo/mg/store.go b/src/margo.sh/mg/store.go similarity index 100% rename from src/disposa.blue/margo/mg/store.go rename to src/margo.sh/mg/store.go diff --git a/src/disposa.blue/margo/mg/tasks.go b/src/margo.sh/mg/tasks.go similarity index 100% rename from src/disposa.blue/margo/mg/tasks.go rename to src/margo.sh/mg/tasks.go diff --git a/src/disposa.blue/margo/mg/tempdir.go b/src/margo.sh/mg/tempdir.go similarity index 100% rename from src/disposa.blue/margo/mg/tempdir.go rename to src/margo.sh/mg/tempdir.go diff --git a/src/disposa.blue/margo/mg/tooltips.go b/src/margo.sh/mg/tooltips.go similarity index 100% rename from src/disposa.blue/margo/mg/tooltips.go rename to src/margo.sh/mg/tooltips.go diff --git a/src/disposa.blue/margo/mg/view.go b/src/margo.sh/mg/view.go similarity index 100% rename from src/disposa.blue/margo/mg/view.go rename to src/margo.sh/mg/view.go diff --git a/src/disposa.blue/margo/mg/view_test.go b/src/margo.sh/mg/view_test.go similarity index 100% rename from src/disposa.blue/margo/mg/view_test.go rename to src/margo.sh/mg/view_test.go diff --git a/src/disposa.blue/margo/mgcli/mgcli.go b/src/margo.sh/mgcli/mgcli.go similarity index 89% rename from src/disposa.blue/margo/mgcli/mgcli.go rename to src/margo.sh/mgcli/mgcli.go index cd9bb1ce..83e741a5 100644 --- a/src/disposa.blue/margo/mgcli/mgcli.go +++ b/src/margo.sh/mgcli/mgcli.go @@ -6,6 +6,12 @@ import ( "os" ) +type Commands struct { + Name string + Build *cli.Command + Run *cli.Command +} + type App struct{ cli.App } func (a *App) RunAndExitOnError() { diff --git a/src/disposa.blue/margo/misc/pprof/pprofdo/pprofdo-fallback.go b/src/margo.sh/misc/pprof/pprofdo/pprofdo-fallback.go similarity index 100% rename from src/disposa.blue/margo/misc/pprof/pprofdo/pprofdo-fallback.go rename to src/margo.sh/misc/pprof/pprofdo/pprofdo-fallback.go diff --git a/src/disposa.blue/margo/misc/pprof/pprofdo/pprofdo.go b/src/margo.sh/misc/pprof/pprofdo/pprofdo.go similarity index 100% rename from src/disposa.blue/margo/misc/pprof/pprofdo/pprofdo.go rename to src/margo.sh/misc/pprof/pprofdo/pprofdo.go diff --git a/src/disposa.blue/margo/misc/pprof/pprofhttp/pprofhttp.go b/src/margo.sh/misc/pprof/pprofhttp/pprofhttp.go similarity index 100% rename from src/disposa.blue/margo/misc/pprof/pprofhttp/pprofhttp.go rename to src/margo.sh/misc/pprof/pprofhttp/pprofhttp.go diff --git a/src/disposa.blue/margo/sublime/config.go b/src/margo.sh/sublime/config.go similarity index 98% rename from src/disposa.blue/margo/sublime/config.go rename to src/margo.sh/sublime/config.go index 5299edad..54d2a73d 100644 --- a/src/disposa.blue/margo/sublime/config.go +++ b/src/margo.sh/sublime/config.go @@ -1,7 +1,7 @@ package sublime import ( - "disposa.blue/margo/mg" + "margo.sh/mg" ) var ( diff --git a/src/disposa.blue/margo/sublime/ext.go b/src/margo.sh/sublime/ext.go similarity index 69% rename from src/disposa.blue/margo/sublime/ext.go rename to src/margo.sh/sublime/ext.go index 848ddd23..59930b85 100644 --- a/src/disposa.blue/margo/sublime/ext.go +++ b/src/margo.sh/sublime/ext.go @@ -1,7 +1,7 @@ package sublime import ( - "disposa.blue/margo/mg" + "margo.sh/mg" ) func Margo(mo mg.Args) { diff --git a/src/margo.sh/sublime/sublime.go b/src/margo.sh/sublime/sublime.go new file mode 100644 index 00000000..6d734140 --- /dev/null +++ b/src/margo.sh/sublime/sublime.go @@ -0,0 +1,125 @@ +package sublime + +import ( + "bytes" + "fmt" + "github.com/urfave/cli" + "go/build" + "io/ioutil" + "margo.sh/cmdpkg/margo/cmdrunner" + "margo.sh/mg" + "margo.sh/mgcli" + "os" + "os/exec" + "path/filepath" +) + +const ( + AgentName = "margo.sublime" +) + +var ( + Commands = mgcli.Commands{ + Name: AgentName, + Build: &cli.Command{ + Action: mgcli.Action(buildAction), + }, + Run: &cli.Command{ + SkipFlagParsing: true, + SkipArgReorder: true, + Action: mgcli.Action(runAction), + }, + } + + logger = mg.NewLogger(os.Stderr) +) + +func buildAction(c *cli.Context) error { + tags := "margo" + pkg := extensionPkg() + if pkg != nil { + fixExtPkg(pkg) + tags = "margo margo_extension" + } + err := goInstallAgent(os.Getenv("MARGO_SUBLIME_GOPATH"), tags) + if err != nil { + return fmt.Errorf("check console for errors: %s", err) + } + return nil +} + +func runAction(c *cli.Context) error { + name := AgentName + if exe, err := exec.LookPath(name); err == nil { + name = exe + } + return cmdrunner.Cmd{Name: name, Args: c.Args()}.Run() +} + +func goInstallAgent(gp string, tags string) error { + args := []string{"install", "-v", "-tags=" + tags} + if os.Getenv("MARGO_BUILD_FLAGS_RACE") == "1" { + args = append(args, "-race") + } + for _, tag := range build.Default.ReleaseTags { + if tag == "go1.10" { + args = append(args, "-i") + break + } + } + args = append(args, "margo.sh/cmd/"+AgentName) + cr := cmdrunner.Cmd{ + Name: "go", + Args: args, + OutToErr: true, + } + if gp != "" { + cr.Env = map[string]string{"GOPATH": gp} + } + return cr.Run() +} + +func extensionPkg() *build.Package { + pkg, err := build.Import("margo", "", 0) + if err != nil || len(pkg.GoFiles) == 0 { + return nil + } + return pkg +} + +func fixExtPkg(pkg *build.Package) { + for _, fn := range pkg.GoFiles { + fixExtFile(filepath.Join(pkg.Dir, fn)) + } +} + +func fixExtFile(fn string) { + p, err := ioutil.ReadFile(fn) + if err != nil { + logger.Println("fixExtFile:", err) + return + } + + from := `disposa.blue/margo` + to := `margo.sh` + q := bytes.Replace(p, []byte(from), []byte(to), -1) + if bytes.Equal(p, q) { + return + } + + bak := fn + ".~mgfix~.bak" + errOk := func(err error) string { + if err != nil { + return err.Error() + } + return "ok" + } + + logger.Printf("mgfix %s: replace `%s` -> `%s`\n", fn, from, to) + err = os.Rename(fn, bak) + logger.Printf("mgfix %s: rename -> `%s`: %s\n", fn, bak, errOk(err)) + if err == nil { + err := ioutil.WriteFile(fn, q, 0644) + logger.Printf("mgfix %s: saving: %s\n", fn, errOk(err)) + } +} diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/LICENSE b/src/margo.sh/vendor/github.com/ugorji/go/LICENSE similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/LICENSE rename to src/margo.sh/vendor/github.com/ugorji/go/LICENSE diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/0doc.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/0doc.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/0doc.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/0doc.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/README.md b/src/margo.sh/vendor/github.com/ugorji/go/codec/README.md similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/README.md rename to src/margo.sh/vendor/github.com/ugorji/go/codec/README.md diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/binc.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/binc.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/binc.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/binc.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/cbor.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/cbor.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/cbor.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/cbor.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/decode.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/decode.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/decode.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/decode.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/encode.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/encode.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/encode.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/encode.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/fast-path.generated.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/fast-path.generated.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/fast-path.generated.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/fast-path.generated.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/fast-path.go.tmpl b/src/margo.sh/vendor/github.com/ugorji/go/codec/fast-path.go.tmpl similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/fast-path.go.tmpl rename to src/margo.sh/vendor/github.com/ugorji/go/codec/fast-path.go.tmpl diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/fast-path.not.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/fast-path.not.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/fast-path.not.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/fast-path.not.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen-dec-array.go.tmpl b/src/margo.sh/vendor/github.com/ugorji/go/codec/gen-dec-array.go.tmpl similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen-dec-array.go.tmpl rename to src/margo.sh/vendor/github.com/ugorji/go/codec/gen-dec-array.go.tmpl diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen-dec-map.go.tmpl b/src/margo.sh/vendor/github.com/ugorji/go/codec/gen-dec-map.go.tmpl similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen-dec-map.go.tmpl rename to src/margo.sh/vendor/github.com/ugorji/go/codec/gen-dec-map.go.tmpl diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen-enc-chan.go.tmpl b/src/margo.sh/vendor/github.com/ugorji/go/codec/gen-enc-chan.go.tmpl similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen-enc-chan.go.tmpl rename to src/margo.sh/vendor/github.com/ugorji/go/codec/gen-enc-chan.go.tmpl diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen-helper.generated.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/gen-helper.generated.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen-helper.generated.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/gen-helper.generated.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen-helper.go.tmpl b/src/margo.sh/vendor/github.com/ugorji/go/codec/gen-helper.go.tmpl similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen-helper.go.tmpl rename to src/margo.sh/vendor/github.com/ugorji/go/codec/gen-helper.go.tmpl diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen.generated.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/gen.generated.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen.generated.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/gen.generated.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/gen.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/gen.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/gen.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_makemap_gte_go19.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_makemap_gte_go19.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_makemap_gte_go19.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_makemap_gte_go19.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_makemap_lt_go19.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_makemap_lt_go19.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_makemap_lt_go19.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_makemap_lt_go19.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_gte_go110.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_gte_go110.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_gte_go110.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_gte_go110.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_lt_go110.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_lt_go110.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_lt_go110.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_lt_go110.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_unsupported_lt_go14.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_unsupported_lt_go14.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_unsupported_lt_go14.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_unsupported_lt_go14.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go15.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go15.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go15.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go15.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go16.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go16.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go16.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go16.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_vendor_gte_go17.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_vendor_gte_go17.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_vendor_gte_go17.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_vendor_gte_go17.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_vendor_lt_go15.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_vendor_lt_go15.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/goversion_vendor_lt_go15.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/goversion_vendor_lt_go15.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/helper.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/helper.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/helper.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/helper.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/helper_internal.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/helper_internal.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/helper_internal.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/helper_internal.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/helper_not_unsafe.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/helper_not_unsafe.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/helper_not_unsafe.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/helper_not_unsafe.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/helper_unsafe.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/helper_unsafe.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/helper_unsafe.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/helper_unsafe.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/json.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/json.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/json.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/json.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/mammoth-test.go.tmpl b/src/margo.sh/vendor/github.com/ugorji/go/codec/mammoth-test.go.tmpl similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/mammoth-test.go.tmpl rename to src/margo.sh/vendor/github.com/ugorji/go/codec/mammoth-test.go.tmpl diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/mammoth2-test.go.tmpl b/src/margo.sh/vendor/github.com/ugorji/go/codec/mammoth2-test.go.tmpl similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/mammoth2-test.go.tmpl rename to src/margo.sh/vendor/github.com/ugorji/go/codec/mammoth2-test.go.tmpl diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/msgpack.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/msgpack.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/msgpack.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/msgpack.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/rpc.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/rpc.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/rpc.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/rpc.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/simple.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/simple.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/simple.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/simple.go diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/test-cbor-goldens.json b/src/margo.sh/vendor/github.com/ugorji/go/codec/test-cbor-goldens.json similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/test-cbor-goldens.json rename to src/margo.sh/vendor/github.com/ugorji/go/codec/test-cbor-goldens.json diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/test.py b/src/margo.sh/vendor/github.com/ugorji/go/codec/test.py similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/test.py rename to src/margo.sh/vendor/github.com/ugorji/go/codec/test.py diff --git a/src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/xml.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/xml.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/ugorji/go/codec/xml.go rename to src/margo.sh/vendor/github.com/ugorji/go/codec/xml.go diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/.flake8 b/src/margo.sh/vendor/github.com/urfave/cli/.flake8 similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/.flake8 rename to src/margo.sh/vendor/github.com/urfave/cli/.flake8 diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/.gitignore b/src/margo.sh/vendor/github.com/urfave/cli/.gitignore similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/.gitignore rename to src/margo.sh/vendor/github.com/urfave/cli/.gitignore diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/.travis.yml b/src/margo.sh/vendor/github.com/urfave/cli/.travis.yml similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/.travis.yml rename to src/margo.sh/vendor/github.com/urfave/cli/.travis.yml diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/CHANGELOG.md b/src/margo.sh/vendor/github.com/urfave/cli/CHANGELOG.md similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/CHANGELOG.md rename to src/margo.sh/vendor/github.com/urfave/cli/CHANGELOG.md diff --git a/src/margo.sh/vendor/github.com/urfave/cli/CODE_OF_CONDUCT.md b/src/margo.sh/vendor/github.com/urfave/cli/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..41ba294f --- /dev/null +++ b/src/margo.sh/vendor/github.com/urfave/cli/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be +reviewed and investigated and will result in a response that is deemed necessary +and appropriate to the circumstances. The project team is obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + diff --git a/src/margo.sh/vendor/github.com/urfave/cli/CONTRIBUTING.md b/src/margo.sh/vendor/github.com/urfave/cli/CONTRIBUTING.md new file mode 100644 index 00000000..329195ee --- /dev/null +++ b/src/margo.sh/vendor/github.com/urfave/cli/CONTRIBUTING.md @@ -0,0 +1,19 @@ +## Contributing + +**NOTE**: the primary maintainer(s) may be found in +[./MAINTAINERS.md](./MAINTAINERS.md). + +Feel free to put up a pull request to fix a bug or maybe add a feature. I will +give it a code review and make sure that it does not break backwards +compatibility. If I or any other collaborators agree that it is in line with +the vision of the project, we will work with you to get the code into +a mergeable state and merge it into the master branch. + +If you have contributed something significant to the project, we will most +likely add you as a collaborator. As a collaborator you are given the ability +to merge others pull requests. It is very important that new code does not +break existing code, so be careful about what code you do choose to merge. + +If you feel like you have contributed to the project but have not yet been added +as a collaborator, we probably forgot to add you :sweat_smile:. Please open an +issue! diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/LICENSE b/src/margo.sh/vendor/github.com/urfave/cli/LICENSE similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/LICENSE rename to src/margo.sh/vendor/github.com/urfave/cli/LICENSE diff --git a/src/margo.sh/vendor/github.com/urfave/cli/MAINTAINERS.md b/src/margo.sh/vendor/github.com/urfave/cli/MAINTAINERS.md new file mode 100644 index 00000000..f6bdd99c --- /dev/null +++ b/src/margo.sh/vendor/github.com/urfave/cli/MAINTAINERS.md @@ -0,0 +1 @@ +- @meatballhat diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/README.md b/src/margo.sh/vendor/github.com/urfave/cli/README.md similarity index 88% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/README.md rename to src/margo.sh/vendor/github.com/urfave/cli/README.md index 2bbbd8ea..f2baef4f 100644 --- a/src/disposa.blue/margo/vendor/github.com/urfave/cli/README.md +++ b/src/margo.sh/vendor/github.com/urfave/cli/README.md @@ -9,9 +9,9 @@ cli [![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / [![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) -**Notice:** This is the library formerly known as -`github.com/codegangsta/cli` -- Github will automatically redirect requests -to this repository, but we recommend updating your references for clarity. +This is the library formerly known as `github.com/codegangsta/cli` -- Github +will automatically redirect requests to this repository, but we recommend +updating your references for clarity. cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line @@ -32,7 +32,9 @@ applications in an expressive way. + [Alternate Names](#alternate-names) + [Ordering](#ordering) + [Values from the Environment](#values-from-the-environment) + + [Values from files](#values-from-files) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + + [Precedence](#precedence) * [Subcommands](#subcommands) * [Subcommands categories](#subcommands-categories) * [Exit code](#exit-code) @@ -45,6 +47,7 @@ applications in an expressive way. * [Version Flag](#version-flag) + [Customization](#customization-2) + [Full API Example](#full-api-example) + * [Combining short Bool options](#combining-short-bool-options) - [Contribution Guidelines](#contribution-guidelines) @@ -138,13 +141,17 @@ discovery. So a cli app can be as little as one line of code in `main()`. package main import ( + "log" "os" "github.com/urfave/cli" ) func main() { - cli.NewApp().Run(os.Args) + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -159,6 +166,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -173,7 +181,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -197,6 +208,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -211,7 +223,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -260,6 +275,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -273,7 +289,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -289,6 +308,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -318,7 +338,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -332,6 +355,7 @@ scanned. package main import ( + "log" "os" "fmt" @@ -365,7 +389,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -386,6 +413,7 @@ For example this: package main import ( + "log" "os" "github.com/urfave/cli" @@ -401,7 +429,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -427,6 +458,7 @@ list for the `Name`. e.g. package main import ( + "log" "os" "github.com/urfave/cli" @@ -443,7 +475,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -467,6 +502,7 @@ For example this: package main import ( + "log" "os" "sort" @@ -510,7 +546,10 @@ func main() { sort.Sort(cli.FlagsByName(app.Flags)) sort.Sort(cli.CommandsByName(app.Commands)) - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -533,6 +572,7 @@ You can also have the default value set from the environment via `EnvVar`. e.g. package main import ( + "log" "os" "github.com/urfave/cli" @@ -550,7 +590,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -565,6 +608,7 @@ environment variable that resolves is used as the default. package main import ( + "log" "os" "github.com/urfave/cli" @@ -582,10 +626,52 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` +#### Values from files + +You can also have the default value set from file via `FilePath`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "password, p", + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Note that default values set from file (e.g. `FilePath`) take precedence over +default values set from the enviornment (e.g. `EnvVar`). + #### Values from alternate input sources (YAML, TOML, and others) There is a separate package altsrc that adds support for getting flag values @@ -615,9 +701,9 @@ the yaml input source for any flags that are defined on that command. As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work. -Currently only the aboved specified formats are supported but developers can -add support for other input sources by implementing the -altsrc.InputSourceContext for their given sources. +Currently only YAML and JSON files are supported but developers can add support +for other input sources by implementing the altsrc.InputSourceContext for their +given sources. Here is a more complete sample of a command using YAML support: @@ -630,6 +716,7 @@ package notmain import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -652,10 +739,22 @@ func main() { app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) app.Flags = flags - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` +#### Precedence + +The precedence for flag value sources is as follows (highest to lowest): + +0. Command line flag value from user +0. Environment variable (if specified) +0. Configuration file (if specified) +0. Default defined on the flag + ### Subcommands Subcommands can be defined for a more git-like command line app. @@ -669,6 +768,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -721,7 +821,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -737,6 +840,7 @@ E.g. package main import ( + "log" "os" "github.com/urfave/cli" @@ -751,15 +855,18 @@ func main() { }, { Name: "add", - Category: "template", + Category: "Template actions", }, { Name: "remove", - Category: "template", + Category: "Template actions", }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -785,6 +892,7 @@ may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a package main import ( + "log" "os" "github.com/urfave/cli" @@ -805,7 +913,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -825,6 +936,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -856,7 +968,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -896,6 +1011,7 @@ The default bash completion flag (`--generate-bash-completion`) is defined as package main import ( + "log" "os" "github.com/urfave/cli" @@ -914,7 +1030,10 @@ func main() { Name: "wat", }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -940,6 +1059,7 @@ package main import ( "fmt" + "log" "io" "os" @@ -983,7 +1103,10 @@ VERSION: fmt.Println("Ha HA. I pwnd the help!!1") } - cli.NewApp().Run(os.Args) + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -998,6 +1121,7 @@ setting `cli.HelpFlag`, e.g.: package main import ( + "log" "os" "github.com/urfave/cli" @@ -1010,7 +1134,10 @@ func main() { EnvVar: "SHOW_HALP,HALPPLZ", } - cli.NewApp().Run(os.Args) + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -1033,6 +1160,7 @@ setting `cli.VersionFlag`, e.g.: package main import ( + "log" "os" "github.com/urfave/cli" @@ -1047,7 +1175,10 @@ func main() { app := cli.NewApp() app.Name = "partay" app.Version = "19.99.0" - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -1062,6 +1193,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -1079,7 +1211,10 @@ func main() { app := cli.NewApp() app.Name = "partay" app.Version = "19.99.0" - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -1341,7 +1476,7 @@ func main() { ec := cli.NewExitError("ohwell", 86) fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) fmt.Printf("made it!\n") - return ec + return nil } if os.Getenv("HEXY") != "" { @@ -1355,7 +1490,9 @@ func main() { "whatever-values": 19.99, } - app.Run(os.Args) + + // ignore error so we don't exit non-zero and break gfmrun README example tests + _ = app.Run(os.Args) } func wopAction(c *cli.Context) error { @@ -1364,18 +1501,26 @@ func wopAction(c *cli.Context) error { } ``` -## Contribution Guidelines +### Combining short Bool options -Feel free to put up a pull request to fix a bug or maybe add a feature. I will -give it a code review and make sure that it does not break backwards -compatibility. If I or any other collaborators agree that it is in line with -the vision of the project, we will work with you to get the code into -a mergeable state and merge it into the master branch. +Traditional use of boolean options using their shortnames look like this: +``` +# cmd foobar -s -o +``` -If you have contributed something significant to the project, we will most -likely add you as a collaborator. As a collaborator you are given the ability -to merge others pull requests. It is very important that new code does not -break existing code, so be careful about what code you do choose to merge. +Suppose you want users to be able to combine your bool options with their shortname. This +can be done using the **UseShortOptionHandling** bool in your commands. Suppose your program +has a two bool flags such as *serve* and *option* with the short options of *-o* and +*-s* respectively. With **UseShortOptionHandling** set to *true*, a user can use a syntax +like: +``` +# cmd foobar -so +``` + +If you enable the **UseShortOptionHandling*, then you must not use any flags that have a single +leading *-* or this will result in failures. For example, **-option** can no longer be used. Flags +with two leading dashes (such as **--options**) are still valid. + +## Contribution Guidelines -If you feel like you have contributed to the project but have not yet been -added as a collaborator, we probably forgot to add you, please open an issue. +See [./CONTRIBUTING.md](./CONTRIBUTING.md) diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/app.go b/src/margo.sh/vendor/github.com/urfave/cli/app.go similarity index 94% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/app.go rename to src/margo.sh/vendor/github.com/urfave/cli/app.go index 51fc45d8..9add067b 100644 --- a/src/disposa.blue/margo/vendor/github.com/urfave/cli/app.go +++ b/src/margo.sh/vendor/github.com/urfave/cli/app.go @@ -83,6 +83,9 @@ type App struct { Writer io.Writer // ErrWriter writes error output ErrWriter io.Writer + // Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to + // function as a default, so this is optional. + ExitErrHandler ExitErrHandlerFunc // Other custom info Metadata map[string]interface{} // Carries a function which returns app specific info. @@ -207,7 +210,7 @@ func (a *App) Run(arguments []string) (err error) { if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -240,8 +243,9 @@ func (a *App) Run(arguments []string) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { + fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) ShowAppHelp(context) - HandleExitCoder(beforeErr) + a.handleExitCoder(context, beforeErr) err = beforeErr return err } @@ -263,7 +267,7 @@ func (a *App) Run(arguments []string) (err error) { // Run default Action err = HandleAction(a.Action, context) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } @@ -330,7 +334,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if err != nil { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -352,7 +356,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { defer func() { afterErr := a.After(context) if afterErr != nil { - HandleExitCoder(err) + a.handleExitCoder(context, err) if err != nil { err = NewMultiError(err, afterErr) } else { @@ -365,7 +369,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - HandleExitCoder(beforeErr) + a.handleExitCoder(context, beforeErr) err = beforeErr return err } @@ -383,7 +387,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { // Run default Action err = HandleAction(a.Action, context) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } @@ -449,7 +453,6 @@ func (a *App) hasFlag(flag Flag) bool { } func (a *App) errWriter() io.Writer { - // When the app ErrWriter is nil use the package level one. if a.ErrWriter == nil { return ErrWriter @@ -464,6 +467,14 @@ func (a *App) appendFlag(flag Flag) { } } +func (a *App) handleExitCoder(context *Context, err error) { + if a.ExitErrHandler != nil { + a.ExitErrHandler(context, err) + } else { + HandleExitCoder(err) + } +} + // Author represents someone who has contributed to a cli project. type Author struct { Name string // The Authors name @@ -491,7 +502,7 @@ func HandleAction(action interface{}, context *Context) (err error) { } else if a, ok := action.(func(*Context)); ok { // deprecated function signature a(context) return nil - } else { - return errInvalidActionType } + + return errInvalidActionType } diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/appveyor.yml b/src/margo.sh/vendor/github.com/urfave/cli/appveyor.yml similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/appveyor.yml rename to src/margo.sh/vendor/github.com/urfave/cli/appveyor.yml diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/category.go b/src/margo.sh/vendor/github.com/urfave/cli/category.go similarity index 95% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/category.go rename to src/margo.sh/vendor/github.com/urfave/cli/category.go index 1a605502..bf3c73c5 100644 --- a/src/disposa.blue/margo/vendor/github.com/urfave/cli/category.go +++ b/src/margo.sh/vendor/github.com/urfave/cli/category.go @@ -10,7 +10,7 @@ type CommandCategory struct { } func (c CommandCategories) Less(i, j int) bool { - return c[i].Name < c[j].Name + return lexicographicLess(c[i].Name, c[j].Name) } func (c CommandCategories) Len() int { diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/cli.go b/src/margo.sh/vendor/github.com/urfave/cli/cli.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/cli.go rename to src/margo.sh/vendor/github.com/urfave/cli/cli.go diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/command.go b/src/margo.sh/vendor/github.com/urfave/cli/command.go similarity index 75% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/command.go rename to src/margo.sh/vendor/github.com/urfave/cli/command.go index 23de2944..2acb9768 100644 --- a/src/disposa.blue/margo/vendor/github.com/urfave/cli/command.go +++ b/src/margo.sh/vendor/github.com/urfave/cli/command.go @@ -1,6 +1,7 @@ package cli import ( + "flag" "fmt" "io/ioutil" "sort" @@ -55,6 +56,10 @@ type Command struct { HideHelp bool // Boolean to hide this command from help or completion Hidden bool + // Boolean to enable short-option handling so user can combine several + // single-character bool arguements into one + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool // Full name of command for help, defaults to full command name, including parent commands. HelpName string @@ -73,7 +78,7 @@ func (c CommandsByName) Len() int { } func (c CommandsByName) Less(i, j int) bool { - return c[i].Name < c[j].Name + return lexicographicLess(c[i].Name, c[j].Name) } func (c CommandsByName) Swap(i, j int) { @@ -106,57 +111,7 @@ func (c Command) Run(ctx *Context) (err error) { ) } - set, err := flagSet(c.Name, c.Flags) - if err != nil { - return err - } - set.SetOutput(ioutil.Discard) - - if c.SkipFlagParsing { - err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) - } else if !c.SkipArgReorder { - firstFlagIndex := -1 - terminatorIndex := -1 - for index, arg := range ctx.Args() { - if arg == "--" { - terminatorIndex = index - break - } else if arg == "-" { - // Do nothing. A dash alone is not really a flag. - continue - } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { - firstFlagIndex = index - } - } - - if firstFlagIndex > -1 { - args := ctx.Args() - regularArgs := make([]string, len(args[1:firstFlagIndex])) - copy(regularArgs, args[1:firstFlagIndex]) - - var flagArgs []string - if terminatorIndex > -1 { - flagArgs = args[firstFlagIndex:terminatorIndex] - regularArgs = append(regularArgs, args[terminatorIndex:]...) - } else { - flagArgs = args[firstFlagIndex:] - } - - err = set.Parse(append(flagArgs, regularArgs...)) - } else { - err = set.Parse(ctx.Args().Tail()) - } - } else { - err = set.Parse(ctx.Args().Tail()) - } - - nerr := normalizeFlags(c.Flags, set) - if nerr != nil { - fmt.Fprintln(ctx.App.Writer, nerr) - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) - return nerr - } + set, err := c.parseFlags(ctx.Args().Tail()) context := NewContext(ctx.App, set, ctx) context.Command = c @@ -167,7 +122,7 @@ func (c Command) Run(ctx *Context) (err error) { if err != nil { if c.OnUsageError != nil { err := c.OnUsageError(context, err, false) - HandleExitCoder(err) + context.App.handleExitCoder(context, err) return err } fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) @@ -184,7 +139,7 @@ func (c Command) Run(ctx *Context) (err error) { defer func() { afterErr := c.After(context) if afterErr != nil { - HandleExitCoder(err) + context.App.handleExitCoder(context, err) if err != nil { err = NewMultiError(err, afterErr) } else { @@ -198,7 +153,7 @@ func (c Command) Run(ctx *Context) (err error) { err = c.Before(context) if err != nil { ShowCommandHelp(context, c.Name) - HandleExitCoder(err) + context.App.handleExitCoder(context, err) return err } } @@ -210,11 +165,88 @@ func (c Command) Run(ctx *Context) (err error) { err = HandleAction(c.Action, context) if err != nil { - HandleExitCoder(err) + context.App.handleExitCoder(context, err) } return err } +func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { + set, err := flagSet(c.Name, c.Flags) + if err != nil { + return nil, err + } + set.SetOutput(ioutil.Discard) + + if c.SkipFlagParsing { + return set, set.Parse(append([]string{"--"}, args...)) + } + + if c.UseShortOptionHandling { + args = translateShortOptions(args) + } + + if !c.SkipArgReorder { + args = reorderArgs(args) + } + + err = set.Parse(args) + if err != nil { + return nil, err + } + + err = normalizeFlags(c.Flags, set) + if err != nil { + return nil, err + } + + return set, nil +} + +// reorderArgs moves all flags before arguments as this is what flag expects +func reorderArgs(args []string) []string { + var nonflags, flags []string + + readFlagValue := false + for i, arg := range args { + if arg == "--" { + nonflags = append(nonflags, args[i:]...) + break + } + + if readFlagValue && !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") { + readFlagValue = false + flags = append(flags, arg) + continue + } + readFlagValue = false + + if arg != "-" && strings.HasPrefix(arg, "-") { + flags = append(flags, arg) + + readFlagValue = !strings.Contains(arg, "=") + } else { + nonflags = append(nonflags, arg) + } + } + + return append(flags, nonflags...) +} + +func translateShortOptions(flagArgs Args) []string { + // separate combined flags + var flagArgsSeparated []string + for _, flagArg := range flagArgs { + if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 { + for _, flagChar := range flagArg[1:] { + flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar)) + } + } else { + flagArgsSeparated = append(flagArgsSeparated, flagArg) + } + } + return flagArgsSeparated +} + // Names returns the names including short names and aliases. func (c Command) Names() []string { names := []string{c.Name} diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/context.go b/src/margo.sh/vendor/github.com/urfave/cli/context.go similarity index 92% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/context.go rename to src/margo.sh/vendor/github.com/urfave/cli/context.go index db94191e..552ee740 100644 --- a/src/disposa.blue/margo/vendor/github.com/urfave/cli/context.go +++ b/src/margo.sh/vendor/github.com/urfave/cli/context.go @@ -3,6 +3,7 @@ package cli import ( "errors" "flag" + "os" "reflect" "strings" "syscall" @@ -73,7 +74,7 @@ func (c *Context) IsSet(name string) bool { // change in version 2 to add `IsSet` to the Flag interface to push the // responsibility closer to where the information required to determine // whether a flag is set by non-standard means such as environment - // variables is avaliable. + // variables is available. // // See https://github.com/urfave/cli/issues/294 for additional discussion flags := c.Command.Flags @@ -93,18 +94,26 @@ func (c *Context) IsSet(name string) bool { val = val.Elem() } - envVarValue := val.FieldByName("EnvVar") - if !envVarValue.IsValid() { - return + filePathValue := val.FieldByName("FilePath") + if filePathValue.IsValid() { + eachName(filePathValue.String(), func(filePath string) { + if _, err := os.Stat(filePath); err == nil { + c.setFlags[name] = true + return + } + }) } - eachName(envVarValue.String(), func(envVar string) { - envVar = strings.TrimSpace(envVar) - if _, ok := syscall.Getenv(envVar); ok { - c.setFlags[name] = true - return - } - }) + envVarValue := val.FieldByName("EnvVar") + if envVarValue.IsValid() { + eachName(envVarValue.String(), func(envVar string) { + envVar = strings.TrimSpace(envVar) + if _, ok := syscall.Getenv(envVar); ok { + c.setFlags[name] = true + return + } + }) + } }) } } diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/errors.go b/src/margo.sh/vendor/github.com/urfave/cli/errors.go similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/errors.go rename to src/margo.sh/vendor/github.com/urfave/cli/errors.go diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/flag-types.json b/src/margo.sh/vendor/github.com/urfave/cli/flag-types.json similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/flag-types.json rename to src/margo.sh/vendor/github.com/urfave/cli/flag-types.json diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/flag.go b/src/margo.sh/vendor/github.com/urfave/cli/flag.go similarity index 70% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/flag.go rename to src/margo.sh/vendor/github.com/urfave/cli/flag.go index 877ff352..b0cffc00 100644 --- a/src/disposa.blue/margo/vendor/github.com/urfave/cli/flag.go +++ b/src/margo.sh/vendor/github.com/urfave/cli/flag.go @@ -3,6 +3,7 @@ package cli import ( "flag" "fmt" + "io/ioutil" "reflect" "runtime" "strconv" @@ -37,6 +38,18 @@ var HelpFlag Flag = BoolFlag{ // to display a flag. var FlagStringer FlagStringFunc = stringifyFlag +// FlagNamePrefixer converts a full flag name and its placeholder into the help +// message flag prefix. This is used by the default FlagStringer. +var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames + +// FlagEnvHinter annotates flag help message with the environment variable +// details. This is used by the default FlagStringer. +var FlagEnvHinter FlagEnvHintFunc = withEnvHint + +// FlagFileHinter annotates flag help message with the environment variable +// details. This is used by the default FlagStringer. +var FlagFileHinter FlagFileHintFunc = withFileHint + // FlagsByName is a slice of Flag. type FlagsByName []Flag @@ -45,7 +58,7 @@ func (f FlagsByName) Len() int { } func (f FlagsByName) Less(i, j int) bool { - return f[i].GetName() < f[j].GetName() + return lexicographicLess(f[i].GetName(), f[j].GetName()) } func (f FlagsByName) Swap(i, j int) { @@ -112,15 +125,9 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { // provided by the user for parsing by the flag func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { val := f.Value - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if err := val.Set(envVal); err != nil { - return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err) - } - break - } + if fileEnvVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + if err := val.Set(fileEnvVal); err != nil { + return fmt.Errorf("could not parse %s as value for flag %s: %s", fileEnvVal, f.Name, err) } } @@ -163,21 +170,19 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &StringSlice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + newVal := &StringSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) } } + if f.Value == nil { + f.Value = newVal + } else { + *f.Value = *newVal + } } eachName(f.Name, func(name string) { @@ -226,21 +231,19 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &IntSlice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + newVal := &IntSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) } } + if f.Value == nil { + f.Value = newVal + } else { + *f.Value = *newVal + } } eachName(f.Name, func(name string) { @@ -289,21 +292,19 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &Int64Slice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + newVal := &Int64Slice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) } } + if f.Value == nil { + f.Value = newVal + } else { + *f.Value = *newVal + } } eachName(f.Name, func(name string) { @@ -324,23 +325,15 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error { val := false - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if envVal == "" { - val = false - break - } - - envValBool, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) - } - - val = envValBool - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + if envVal == "" { + val = false + } else { + envValBool, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) } + val = envValBool } } @@ -364,23 +357,16 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error { val := true - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if envVal == "" { - val = false - break - } - envValBool, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) - } - - val = envValBool - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + if envVal == "" { + val = false + } else { + envValBool, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) } + val = envValBool } } @@ -403,14 +389,8 @@ func (f StringFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f StringFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - f.Value = envVal - break - } - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + f.Value = envVal } eachName(f.Name, func(name string) { @@ -432,18 +412,12 @@ func (f IntFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) - } - f.Value = int(envValInt) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) } + f.Value = int(envValInt) } eachName(f.Name, func(name string) { @@ -465,19 +439,13 @@ func (f Int64Flag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = envValInt - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) } + + f.Value = envValInt } eachName(f.Name, func(name string) { @@ -499,19 +467,13 @@ func (f UintFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f UintFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = uint(envValInt) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) } + + f.Value = uint(envValInt) } eachName(f.Name, func(name string) { @@ -533,19 +495,13 @@ func (f Uint64Flag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = uint64(envValInt) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) } + + f.Value = uint64(envValInt) } eachName(f.Name, func(name string) { @@ -567,19 +523,13 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValDuration, err := time.ParseDuration(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) - } - - f.Value = envValDuration - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValDuration, err := time.ParseDuration(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) } + + f.Value = envValDuration } eachName(f.Name, func(name string) { @@ -601,19 +551,13 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValFloat, err := strconv.ParseFloat(envVal, 10) - if err != nil { - return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = float64(envValFloat) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValFloat, err := strconv.ParseFloat(envVal, 10) + if err != nil { + return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) } + + f.Value = float64(envValFloat) } eachName(f.Name, func(name string) { @@ -692,11 +636,19 @@ func withEnvHint(envVar, str string) string { suffix = "%" sep = "%, %" } - envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) + envText = " [" + prefix + strings.Join(strings.Split(envVar, ","), sep) + suffix + "]" } return str + envText } +func withFileHint(filePath, str string) string { + fileText := "" + if filePath != "" { + fileText = fmt.Sprintf(" [%s]", filePath) + } + return str + fileText +} + func flagValue(f Flag) reflect.Value { fv := reflect.ValueOf(f) for fv.Kind() == reflect.Ptr { @@ -710,14 +662,29 @@ func stringifyFlag(f Flag) string { switch f.(type) { case IntSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyIntSliceFlag(f.(IntSliceFlag))) + return FlagFileHinter( + fv.FieldByName("FilePath").String(), + FlagEnvHinter( + fv.FieldByName("EnvVar").String(), + stringifyIntSliceFlag(f.(IntSliceFlag)), + ), + ) case Int64SliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyInt64SliceFlag(f.(Int64SliceFlag))) + return FlagFileHinter( + fv.FieldByName("FilePath").String(), + FlagEnvHinter( + fv.FieldByName("EnvVar").String(), + stringifyInt64SliceFlag(f.(Int64SliceFlag)), + ), + ) case StringSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyStringSliceFlag(f.(StringSliceFlag))) + return FlagFileHinter( + fv.FieldByName("FilePath").String(), + FlagEnvHinter( + fv.FieldByName("EnvVar").String(), + stringifyStringSliceFlag(f.(StringSliceFlag)), + ), + ) } placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) @@ -742,17 +709,22 @@ func stringifyFlag(f Flag) string { placeholder = defaultPlaceholder } - usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) + usageWithDefault := strings.TrimSpace(usage + defaultValueString) - return withEnvHint(fv.FieldByName("EnvVar").String(), - fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) + return FlagFileHinter( + fv.FieldByName("FilePath").String(), + FlagEnvHinter( + fv.FieldByName("EnvVar").String(), + FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder)+"\t"+usageWithDefault, + ), + ) } func stringifyIntSliceFlag(f IntSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + defaultVals = append(defaultVals, strconv.Itoa(i)) } } @@ -763,7 +735,7 @@ func stringifyInt64SliceFlag(f Int64SliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) } } @@ -775,7 +747,7 @@ func stringifyStringSliceFlag(f StringSliceFlag) string { if f.Value != nil && len(f.Value.Value()) > 0 { for _, s := range f.Value.Value() { if len(s) > 0 { - defaultVals = append(defaultVals, fmt.Sprintf("%q", s)) + defaultVals = append(defaultVals, strconv.Quote(s)) } } } @@ -794,6 +766,21 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) } - usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault) + usageWithDefault := strings.TrimSpace(usage + defaultVal) + return FlagNamePrefixer(name, placeholder) + "\t" + usageWithDefault +} + +func flagFromFileEnv(filePath, envName string) (val string, ok bool) { + for _, envVar := range strings.Split(envName, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + return envVal, true + } + } + for _, fileVar := range strings.Split(filePath, ",") { + if data, err := ioutil.ReadFile(fileVar); err == nil { + return string(data), true + } + } + return "", false } diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/flag_generated.go b/src/margo.sh/vendor/github.com/urfave/cli/flag_generated.go similarity index 95% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/flag_generated.go rename to src/margo.sh/vendor/github.com/urfave/cli/flag_generated.go index 491b6195..001576c8 100644 --- a/src/disposa.blue/margo/vendor/github.com/urfave/cli/flag_generated.go +++ b/src/margo.sh/vendor/github.com/urfave/cli/flag_generated.go @@ -13,6 +13,7 @@ type BoolFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Destination *bool } @@ -60,6 +61,7 @@ type BoolTFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Destination *bool } @@ -107,6 +109,7 @@ type DurationFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value time.Duration Destination *time.Duration @@ -155,6 +158,7 @@ type Float64Flag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value float64 Destination *float64 @@ -200,11 +204,12 @@ func lookupFloat64(name string, set *flag.FlagSet) float64 { // GenericFlag is a flag with type Generic type GenericFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value Generic + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value Generic } // String returns a readable representation of this value @@ -250,6 +255,7 @@ type Int64Flag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value int64 Destination *int64 @@ -298,6 +304,7 @@ type IntFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value int Destination *int @@ -343,11 +350,12 @@ func lookupInt(name string, set *flag.FlagSet) int { // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *IntSlice + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value *IntSlice } // String returns a readable representation of this value @@ -390,11 +398,12 @@ func lookupIntSlice(name string, set *flag.FlagSet) []int { // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *Int64Slice + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value *Int64Slice } // String returns a readable representation of this value @@ -440,6 +449,7 @@ type StringFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value string Destination *string @@ -485,11 +495,12 @@ func lookupString(name string, set *flag.FlagSet) string { // StringSliceFlag is a flag with type *StringSlice type StringSliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *StringSlice + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value *StringSlice } // String returns a readable representation of this value @@ -535,6 +546,7 @@ type Uint64Flag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value uint64 Destination *uint64 @@ -583,6 +595,7 @@ type UintFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value uint Destination *uint diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/funcs.go b/src/margo.sh/vendor/github.com/urfave/cli/funcs.go similarity index 64% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/funcs.go rename to src/margo.sh/vendor/github.com/urfave/cli/funcs.go index cba5e6cb..0036b113 100644 --- a/src/disposa.blue/margo/vendor/github.com/urfave/cli/funcs.go +++ b/src/margo.sh/vendor/github.com/urfave/cli/funcs.go @@ -23,6 +23,22 @@ type CommandNotFoundFunc func(*Context, string) // is displayed and the execution is interrupted. type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error +// ExitErrHandlerFunc is executed if provided in order to handle ExitError values +// returned by Actions and Before/After functions. +type ExitErrHandlerFunc func(context *Context, err error) + // FlagStringFunc is used by the help generation to display a flag, which is // expected to be a single line. type FlagStringFunc func(Flag) string + +// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix +// text for a flag's full name. +type FlagNamePrefixFunc func(fullName, placeholder string) string + +// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help +// with the environment variable details. +type FlagEnvHintFunc func(envVar, str string) string + +// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help +// with the file path details. +type FlagFileHintFunc func(filePath, str string) string diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/generate-flag-types b/src/margo.sh/vendor/github.com/urfave/cli/generate-flag-types similarity index 99% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/generate-flag-types rename to src/margo.sh/vendor/github.com/urfave/cli/generate-flag-types index 7147381c..13588573 100755 --- a/src/disposa.blue/margo/vendor/github.com/urfave/cli/generate-flag-types +++ b/src/margo.sh/vendor/github.com/urfave/cli/generate-flag-types @@ -142,6 +142,7 @@ def _write_cli_flag_types(outfile, types): Name string Usage string EnvVar string + FilePath string Hidden bool """.format(**typedef)) diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/help.go b/src/margo.sh/vendor/github.com/urfave/cli/help.go similarity index 97% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/help.go rename to src/margo.sh/vendor/github.com/urfave/cli/help.go index 57ec98d5..65874fa2 100644 --- a/src/disposa.blue/margo/vendor/github.com/urfave/cli/help.go +++ b/src/margo.sh/vendor/github.com/urfave/cli/help.go @@ -29,6 +29,7 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{end}}{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} @@ -157,8 +158,14 @@ func DefaultAppComplete(c *Context) { if command.Hidden { continue } - for _, name := range command.Names() { - fmt.Fprintln(c.App.Writer, name) + if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { + for _, name := range command.Names() { + fmt.Fprintf(c.App.Writer, "%s:%s\n", name, command.Usage) + } + } else { + for _, name := range command.Names() { + fmt.Fprintf(c.App.Writer, "%s\n", name) + } } } } diff --git a/src/disposa.blue/margo/vendor/github.com/urfave/cli/runtests b/src/margo.sh/vendor/github.com/urfave/cli/runtests similarity index 100% rename from src/disposa.blue/margo/vendor/github.com/urfave/cli/runtests rename to src/margo.sh/vendor/github.com/urfave/cli/runtests diff --git a/src/margo.sh/vendor/github.com/urfave/cli/sort.go b/src/margo.sh/vendor/github.com/urfave/cli/sort.go new file mode 100644 index 00000000..23d1c2f7 --- /dev/null +++ b/src/margo.sh/vendor/github.com/urfave/cli/sort.go @@ -0,0 +1,29 @@ +package cli + +import "unicode" + +// lexicographicLess compares strings alphabetically considering case. +func lexicographicLess(i, j string) bool { + iRunes := []rune(i) + jRunes := []rune(j) + + lenShared := len(iRunes) + if lenShared > len(jRunes) { + lenShared = len(jRunes) + } + + for index := 0; index < lenShared; index++ { + ir := iRunes[index] + jr := jRunes[index] + + if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr { + return lir < ljr + } + + if ir != jr { + return ir < jr + } + } + + return i < j +} diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/AUTHORS b/src/margo.sh/vendor/golang.org/x/crypto/AUTHORS similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/AUTHORS rename to src/margo.sh/vendor/golang.org/x/crypto/AUTHORS diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/CONTRIBUTORS b/src/margo.sh/vendor/golang.org/x/crypto/CONTRIBUTORS similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/CONTRIBUTORS rename to src/margo.sh/vendor/golang.org/x/crypto/CONTRIBUTORS diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/LICENSE b/src/margo.sh/vendor/golang.org/x/crypto/LICENSE similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/LICENSE rename to src/margo.sh/vendor/golang.org/x/crypto/LICENSE diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/PATENTS b/src/margo.sh/vendor/golang.org/x/crypto/PATENTS similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/PATENTS rename to src/margo.sh/vendor/golang.org/x/crypto/PATENTS diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b.go b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b.go similarity index 76% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b.go rename to src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b.go index 6dedb894..58ea8753 100644 --- a/src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b.go +++ b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b.go @@ -92,6 +92,8 @@ func New256(key []byte) (hash.Hash, error) { return newDigest(Size256, key) } // values equal or greater than: // - 32 if BLAKE2b is used as a hash function (The key is zero bytes long). // - 16 if BLAKE2b is used as a MAC function (The key is at least 16 bytes long). +// When the key is nil, the returned hash.Hash implements BinaryMarshaler +// and BinaryUnmarshaler for state (de)serialization as documented by hash.Hash. func New(size int, key []byte) (hash.Hash, error) { return newDigest(size, key) } func newDigest(hashSize int, key []byte) (*digest, error) { @@ -150,6 +152,50 @@ type digest struct { keyLen int } +const ( + magic = "b2b" + marshaledSize = len(magic) + 8*8 + 2*8 + 1 + BlockSize + 1 +) + +func (d *digest) MarshalBinary() ([]byte, error) { + if d.keyLen != 0 { + return nil, errors.New("crypto/blake2b: cannot marshal MACs") + } + b := make([]byte, 0, marshaledSize) + b = append(b, magic...) + for i := 0; i < 8; i++ { + b = appendUint64(b, d.h[i]) + } + b = appendUint64(b, d.c[0]) + b = appendUint64(b, d.c[1]) + // Maximum value for size is 64 + b = append(b, byte(d.size)) + b = append(b, d.block[:]...) + b = append(b, byte(d.offset)) + return b, nil +} + +func (d *digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic) || string(b[:len(magic)]) != magic { + return errors.New("crypto/blake2b: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("crypto/blake2b: invalid hash state size") + } + b = b[len(magic):] + for i := 0; i < 8; i++ { + b, d.h[i] = consumeUint64(b) + } + b, d.c[0] = consumeUint64(b) + b, d.c[1] = consumeUint64(b) + d.size = int(b[0]) + b = b[1:] + copy(d.block[:], b[:BlockSize]) + b = b[BlockSize:] + d.offset = int(b[0]) + return nil +} + func (d *digest) BlockSize() int { return BlockSize } func (d *digest) Size() int { return d.size } @@ -219,3 +265,25 @@ func (d *digest) finalize(hash *[Size]byte) { binary.LittleEndian.PutUint64(hash[8*i:], v) } } + +func appendUint64(b []byte, x uint64) []byte { + var a [8]byte + binary.BigEndian.PutUint64(a[:], x) + return append(b, a[:]...) +} + +func appendUint32(b []byte, x uint32) []byte { + var a [4]byte + binary.BigEndian.PutUint32(a[:], x) + return append(b, a[:]...) +} + +func consumeUint64(b []byte) ([]byte, uint64) { + x := binary.BigEndian.Uint64(b) + return b[8:], x +} + +func consumeUint32(b []byte) ([]byte, uint32) { + x := binary.BigEndian.Uint32(b) + return b[4:], x +} diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go rename to src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s rename to src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go rename to src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s rename to src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go rename to src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go rename to src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2x.go b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2x.go similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/blake2x.go rename to src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2x.go diff --git a/src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/register.go b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/register.go similarity index 100% rename from src/disposa.blue/margo/vendor/golang.org/x/crypto/blake2b/register.go rename to src/margo.sh/vendor/golang.org/x/crypto/blake2b/register.go From 398db0e6717e77dbab13ac0a5903f106bfe4c2ff Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Wed, 4 Apr 2018 11:56:36 +0100 Subject: [PATCH 002/186] remove default .html.go extension from gohtml_extensions since the highlighting is wrong --- GoSublime.sublime-settings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoSublime.sublime-settings b/GoSublime.sublime-settings index d51d6333..0fd80764 100644 --- a/GoSublime.sublime-settings +++ b/GoSublime.sublime-settings @@ -325,7 +325,7 @@ // `GoSublime: HTML` files are html files with the template delimiters `{{` and `}}` tailored to // Go templates (text/template, html/template) // (`.gohtml` files are automatically set by the syntax definition) - "gohtml_extensions": [".html.go"], + "gohtml_extensions": [], // Export the listed environment variables to Sublime Text when the GoSublime settings change // so the env vars GS loads through your shell and project are available to other plugins From 969f40b290470d815de77d8440d7c1e136af6f34 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Wed, 4 Apr 2018 13:49:29 +0100 Subject: [PATCH 003/186] sync margo --- src/margo.sh/mg/agent.go | 12 +++++++----- src/margo.sh/mg/store.go | 26 ++++++++++++++++---------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/margo.sh/mg/agent.go b/src/margo.sh/mg/agent.go index 410dd107..bbe89685 100644 --- a/src/margo.sh/mg/agent.go +++ b/src/margo.sh/mg/agent.go @@ -63,12 +63,14 @@ type AgentConfig struct { Stderr io.WriteCloser } +type agentReqAction struct { + Name string +} + type agentReq struct { - Cookie string - Action struct { - Name string - } - Props clientProps + Cookie string + Actions []agentReqAction + Props clientProps } func newAgentReq() *agentReq { diff --git a/src/margo.sh/mg/store.go b/src/margo.sh/mg/store.go index e6740557..cfce828e 100644 --- a/src/margo.sh/mg/store.go +++ b/src/margo.sh/mg/store.go @@ -77,26 +77,32 @@ func (sto *Store) syncRq(ag *Agent, rq *agentReq) { sto.mu.Lock() defer sto.mu.Unlock() - name := rq.Action.Name + rs := agentRes{Cookie: rq.Cookie} + for _, ra := range rq.Actions { + st, err := sto.syncRqAct(ag, rq.Props, ra.Name) + rs.State = st + if err != nil { + rs.Error = err.Error() + } + } + rs.State = sto.updateState(rs.State, false) + ag.send(rs) +} + +func (sto *Store) syncRqAct(ag *Agent, props clientProps, name string) (*State, error) { mx, done := newCtx(sto.ag, sto.state, ag.createAction(name), sto) defer close(done) - rs := agentRes{Cookie: rq.Cookie} - rs.State = mx.State - defer func() { ag.send(rs) }() - if mx.Action == nil { - rs.Error = fmt.Sprintf("unknown client action: %s", name) - return + return mx.State, fmt.Errorf("unknown client action: %s", name) } // TODO: add support for unpacking Action.Data - mx = rq.Props.updateCtx(mx) + mx = props.updateCtx(mx) sto.initCache(mx.View) mx.State = sto.prepState(mx.State) - st := sto.reducers.Reduce(mx) - rs.State = sto.updateState(st, false) + return sto.reducers.Reduce(mx), nil } func (sto *Store) updateState(st *State, callListener bool) *State { From 6bef4f2ebea980969989f9f66482f9bd4bea9a11 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Wed, 4 Apr 2018 13:49:43 +0100 Subject: [PATCH 004/186] batch non-blocking actions like ViewSaved and ViewActivated --- gosubl/margo.py | 38 ++++++++--------- gosubl/margo_agent.py | 91 ++++++++++++++++++++++------------------- gosubl/margo_sublime.py | 8 +--- 3 files changed, 65 insertions(+), 72 deletions(-) diff --git a/gosubl/margo.py b/gosubl/margo.py index 365fc6b0..d82f03ef 100644 --- a/gosubl/margo.py +++ b/gosubl/margo.py @@ -132,13 +132,16 @@ def _send_start(self): if not self.agent: self.start() - def send(self, action={}, cb=None, view=None): + def queue(self, *, actions=[], view=None): self._send_start() - return self.agent.send(action=action, cb=cb, view=view) + self.agent.queue(actions=actions, view=view) + + def send(self, *, actions=[], cb=None, view=None): + self._send_start() + return self.agent.send(actions=actions, cb=cb, view=view) def on_query_completions(self, view, prefix, locations): - action = actions.QueryCompletions.copy() - rs = self.send(view=view, action=action).wait(0.300) + rs = self.send(view=view, actions=[actions.QueryCompletions]).wait(0.300) if not rs: self.out.println('aborting QueryCompletions. it did not respond in time') return None @@ -147,30 +150,24 @@ def on_query_completions(self, view, prefix, locations): opts = rs.state.config.auto_complete_opts return (cl, opts) if opts != 0 else cl - def on_hover(self, view, point, hover_zone): - if hover_zone != sublime.HOVER_TEXT: - return - def on_activated(self, view): - self.send(view=view, action=actions.ViewActivated) + self.queue(view=view, actions=[actions.ViewActivated]) def on_modified(self, view): - self._send_start() - self.agent.view_modified(view) + self.queue(view=view, actions=[actions.ViewModified]) def on_selection_modified(self, view): - self._send_start() - self.agent.view_pos_changed(view) + self.queue(view=view, actions=[actions.ViewPosChanged]) def fmt(self, view): - return self._fmt_save(view=view, action=actions.ViewFmt, name='fmt', timeout=5.000) + return self._fmt_save(view=view, actions=[actions.ViewFmt], name='fmt', timeout=5.000) def on_pre_save(self, view): - return self._fmt_save(view=view, action=actions.ViewPreSave, name='pre-save', timeout=2.000) + return self._fmt_save(view=view, actions=[actions.ViewPreSave], name='pre-save', timeout=2.000) - def _fmt_save(self, *, view, action, name, timeout): + def _fmt_save(self, *, view, actions, name, timeout): id_nm = '%d: %s' % (view.id(), view.file_name() or view.name()) - rq = self.send(view=view, action=action) + rq = self.send(view=view, actions=actions) rs = rq.wait(timeout) if not rs: self.out.println('%s timedout on view %s' % (name, id_nm)) @@ -200,13 +197,10 @@ def _fmt_save(self, *, view, action, name, timeout): view.run_command('margo_render_src', {'src': res_src}) def on_post_save(self, view): - self.send(view=view, action=actions.ViewSaved) + self.queue(view=view, actions=[actions.ViewSaved]) def on_load(self, view): - self.send(view=view, action=actions.ViewLoaded) - - def on_close(self, view): - self.send(view=view, action=actions.ViewClosed) + self.queue(view=view, actions=[actions.ViewLoaded]) def example_extension_file(self): return gs.dist_path('src/margo.sh/extension-example/extension-example.go') diff --git a/gosubl/margo_agent.py b/gosubl/margo_agent.py index 7212e21b..1e3d101a 100644 --- a/gosubl/margo_agent.py +++ b/gosubl/margo_agent.py @@ -52,9 +52,8 @@ def __init__(self, mg): 'PATH': psep.join([os.path.join(p, 'bin') for p in gopaths]) + psep + os.environ.get('PATH'), } - self._mod_ev = threading.Event() - self._mod_view = None - self._pos_view = None + self._acts_lock = threading.Lock() + self._acts = [] def __del__(self): self.stop() @@ -120,7 +119,7 @@ def _start_proc(self): self.proc = pr.p gsq.launch(self.domain, self._handle_send) - gsq.launch(self.domain, self._handle_send_mod) + gsq.launch(self.domain, self._handle_acts) gsq.launch(self.domain, self._handle_recv) gsq.launch(self.domain, self._handle_log) self.started.set() @@ -138,6 +137,7 @@ def _stop_proc(self): f.close() except Exception as exc: self.out.println(exc) + gs.error_traceback(self.domain) def _handle_send_ipc(self, rq): with self.lock: @@ -159,8 +159,35 @@ def _handle_send_ipc(self, rq): rq.done(AgentRes(error='Exception: %s' % exc, rq=rq, agent=self)) - def send(self, action={}, cb=None, view=None): - rq = AgentReq(self, action, cb=cb, view=view) + def _queued_acts(self, view): + with self._acts_lock: + q, self._acts = self._acts, [] + + acts = [] + for act, vid in q: + if vid == view.id(): + acts.append(act) + + return acts + + def queue(self, *, actions=[], view=None): + with self._acts_lock: + for act in actions: + p = (act, view.id()) + try: + self._acts.remove(p) + except ValueError: + pass + + self._acts.append(p) + + def send(self, *, actions=[], cb=None, view=None): + view = gs.active_view(view=view) + acts = self._queued_acts(view) + if not isinstance(actions, list): + raise Exception('actions is %s' % type(actions)) + acts.extend(actions) + rq = AgentReq(self, acts, cb=cb, view=view) timeout = 0.200 if not self.started.wait(timeout): rq.done(AgentRes(error='margo has not started after %0.3fs' % (timeout), timedout=timeout, rq=rq, agent=self)) @@ -171,36 +198,16 @@ def send(self, action={}, cb=None, view=None): return rq - def view_modified(self, view): - self._mod_view = view - self._mod_ev.set() - - def view_pos_changed(self, view): - self._pos_view = view - self._mod_ev.set() - - def _send_mod(self): - mod_v, self._mod_view = self._mod_view, None - pos_v, self._pos_view = self._pos_view, None - if mod_v is None and pos_v is None: - return + def _send_acts(self): + view = gs.active_view() + acts = self._queued_acts(view) + if acts: + self.send(actions=acts, view=view).wait() - view = pos_v - action = actions.ViewPosChanged - if mod_v is not None: - action = actions.ViewModified - view = mod_v - - self.send(action=action, view=view).wait() - - def _handle_send_mod(self): - delay = 0.500 + def _handle_acts(self): while not self.stopped.is_set(): - self._mod_ev.wait(delay) - if self._mod_ev.is_set(): - self._mod_ev.clear() - time.sleep(delay * 1.5) - self._send_mod() + time.sleep(0.750) + self._send_acts() def _handle_send(self): for rq in self.req_chan: @@ -252,6 +259,7 @@ def _handle_recv(self): pass except Exception as e: self.out.println('ipc: recv: %s: %s' % (e, v)) + gs.error_traceback(self.domain) finally: self.stop() @@ -287,22 +295,19 @@ def __init__(self, v={}, error='', timedout=0, rq=None, agent=None): def set_rq(self, rq): if self.error and rq: - act = rq.action - if act and act.get('Name'): - self.error = 'action: %s, error: %s' % (act.get('Name'), self.error) - else: - self.error = 'error: %s' % self.error + self.error = 'actions: %s, error: %s' % (rq.actions_str, self.error) def get(self, k, default=None): return self.state.get(k, default) class AgentReq(object): - def __init__(self, agent, action, cb=None, view=None): + def __init__(self, agent, actions, cb=None, view=None): self.start_time = time.time() + self.actions = actions + self.actions_str = ' ~> '.join(a['Name'] for a in actions) _, cookie = agent.cookies.next() - self.cookie = 'action:%s(%s)' % (action['Name'], cookie) + self.cookie = 'actions(%s),%s' % (self.actions_str, cookie) self.domain = self.cookie - self.action = action self.cb = cb self.props = make_props(view=view) self.rs = DEFAULT_RESPONSE @@ -334,7 +339,7 @@ def data(self): return { 'Cookie': self.cookie, 'Props': self.props, - 'Action': self.action, + 'Actions': self.actions, } DEFAULT_RESPONSE = AgentRes(error='default agent response') diff --git a/gosubl/margo_sublime.py b/gosubl/margo_sublime.py index 99fdcab4..14674598 100644 --- a/gosubl/margo_sublime.py +++ b/gosubl/margo_sublime.py @@ -11,9 +11,6 @@ class MargoEvents(sublime_plugin.EventListener): def on_query_completions(self, view, prefix, locations): return mg.event('query_completions', view, mg.on_query_completions, [view, prefix, locations]) - def on_hover(self, view, point, hover_zone): - return mg.event('hover', view, mg.on_hover, [view, point, hover_zone]) - def on_activated_async(self, view): return mg.event('activated', view, mg.on_activated, [view]) @@ -32,9 +29,6 @@ def on_post_save_async(self, view): def on_load_async(self, view): return mg.event('load', view, mg.on_load, [view]) - def on_close(self, view): - return mg.event('close', view, mg.on_close, [view]) - class MargoRenderSrcCommand(sublime_plugin.TextCommand): def run(self, edit, src): render_src(self.view, edit, src) @@ -49,7 +43,7 @@ def run(self, edit, **action): }) def _run(self): - mg.send(view=self.view, action=actions.QueryIssues, cb=self._cb) + mg.send(view=self.view, actions=[actions.QueryIssues], cb=self._cb) def _cb(self, rs): show_issues(self.view, rs.state.issues) From ecfcd711b42f359d8897d2d631952e7047af4993 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sun, 8 Apr 2018 10:11:31 +0100 Subject: [PATCH 005/186] change the default issue tag to `error` --- gosubl/margo_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gosubl/margo_render.py b/gosubl/margo_render.py index 5918d824..2c796d37 100644 --- a/gosubl/margo_render.py +++ b/gosubl/margo_render.py @@ -56,7 +56,7 @@ def __init__(self, *, key, scope, icon, flags): icon = issue_cfg_error.icon, flags = issue_cfg_error.flags, ) -issue_cfg_default = issue_cfg_warning +issue_cfg_default = issue_cfg_error issue_cfgs = { 'error': issue_cfg_error, 'warning': issue_cfg_warning, From 0a35d7cdc23f3ddd8dfea314878b2898feefcec7 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sun, 8 Apr 2018 10:13:44 +0100 Subject: [PATCH 006/186] sync margo --- src/margo.sh/Gopkg.lock | 4 +- .../extension-example/extension-example.go | 3 + src/margo.sh/format/format.go | 100 ++++++++++ src/margo.sh/golang/completion.go | 5 +- src/margo.sh/golang/gocode.go | 12 +- src/margo.sh/golang/gocode_calltips.go | 181 ++++++++++++++++++ src/margo.sh/golang/gofmt.go | 73 +++---- src/margo.sh/golang/parse.go | 5 +- src/margo.sh/golang/snippets.go | 43 +++-- src/margo.sh/mg/action.go | 3 - src/margo.sh/mg/common.go | 15 ++ src/margo.sh/mg/store.go | 3 +- src/margo.sh/sublime/config.go | 4 + .../github.com/ugorji/go/codec/decode.go | 4 + .../vendor/github.com/ugorji/go/codec/gen.go | 3 +- .../ugorji/go/codec/helper_unsafe.go | 3 +- 16 files changed, 381 insertions(+), 80 deletions(-) create mode 100644 src/margo.sh/format/format.go create mode 100644 src/margo.sh/golang/gocode_calltips.go diff --git a/src/margo.sh/Gopkg.lock b/src/margo.sh/Gopkg.lock index 487396fc..6fde3f9a 100644 --- a/src/margo.sh/Gopkg.lock +++ b/src/margo.sh/Gopkg.lock @@ -5,7 +5,7 @@ branch = "master" name = "github.com/ugorji/go" packages = ["codec"] - revision = "02537d3a3e32ef636a53519265d211bd208ca488" + revision = "f3cacc17c85ecb7f1b6a9e373ee85d1480919868" [[projects]] branch = "master" @@ -17,7 +17,7 @@ branch = "master" name = "golang.org/x/crypto" packages = ["blake2b"] - revision = "12892e8c234f4fe6f6803f052061de9057903bb2" + revision = "b2aa35443fbc700ab74c586ae79b81c171851023" [solve-meta] analyzer-name = "dep" diff --git a/src/margo.sh/extension-example/extension-example.go b/src/margo.sh/extension-example/extension-example.go index 08f0b175..4db30f47 100644 --- a/src/margo.sh/extension-example/extension-example.go +++ b/src/margo.sh/extension-example/extension-example.go @@ -45,6 +45,9 @@ func Margo(ma mg.Args) { ShowFuncParams: true, }, + // show func arguments/calltips in the status bar + &golang.GocodeCalltips{}, + // add some default context aware-ish snippets golang.Snippets, diff --git a/src/margo.sh/format/format.go b/src/margo.sh/format/format.go new file mode 100644 index 00000000..82df8a2c --- /dev/null +++ b/src/margo.sh/format/format.go @@ -0,0 +1,100 @@ +package format + +import ( + "bytes" + "fmt" + "margo.sh/mg" + "os/exec" +) + +// FmtFunc is a reducer for generic fmt functions +// +// it takes care of reading the view src and properly reporting any errors to the editor +type FmtFunc struct { + // Fmt receives a copy of the view src and returns the fmt'ed src. + // + // Fmt should ideally fail in the face of any uncertainty + // e.g. if running a command to do the formatting and it prints anything to stderr; + // it should return an error because commands do not reliably return an error status. + Fmt func(mx *mg.Ctx, src []byte) ([]byte, error) + + // Langs is the list of languages in which the reducer should run + Langs []string + + // Actions is a list of additional actions on which the reducer is allowed to run. + // The reducer always runs on the ViewFmt action, even if this list is empty. + Actions []mg.Action +} + +// Reduce implements the FmtFunc reducer. +func (ff FmtFunc) Reduce(mx *mg.Ctx) *mg.State { + if !mx.LangIs(ff.Langs...) { + return mx.State + } + if !mx.ActionIs(mg.ViewFmt{}) && !mx.ActionIs(ff.Actions...) { + return mx.State + } + + fn := mx.View.Filename() + src, err := mx.View.ReadAll() + if err != nil { + return mx.Errorf("failed to read %s: %s\n", fn, err) + } + if len(src) == 0 { + return mx.State + } + + src, err = ff.Fmt(mx, src) + if err != nil { + return mx.Errorf("failed to fmt %s: %s\n", fn, err) + } + return mx.SetSrc(src) +} + +// FmtCmd is wrapper around FmtFunc for generic fmt commands. +// +// The view src is passed to the command's stdin. +// It takes care of handling command failure e.g. output on stderr or no output on stdout. +type FmtCmd struct { + // Name is the command name or path + Name string + + // Args is a list of args to pass to the command. + Args []string + + // Env is a map of additional env vars to pass to the command. + Env mg.EnvMap + + // Langs is the list of languages in which the reducer should run + Langs []string + + // Actions is a list of additional actions on which the reducer is allowed to run. + // The reducer always runs on the ViewFmt action, even if this list is empty. + Actions []mg.Action +} + +// Reduce implements the FmtCmd reducer. +func (fc FmtCmd) Reduce(mx *mg.Ctx) *mg.State { + return FmtFunc{Fmt: fc.fmt, Langs: fc.Langs, Actions: fc.Actions}.Reduce(mx) +} + +func (fc FmtCmd) fmt(mx *mg.Ctx, src []byte) ([]byte, error) { + stdin := bytes.NewReader(src) + stdout := bytes.NewBuffer(nil) + stderr := bytes.NewBuffer(nil) + cmd := exec.Command(fc.Name, fc.Args...) + cmd.Env = mx.Env.Merge(fc.Env).Environ() + cmd.Stdin = stdin + cmd.Stdout = stdout + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { + return nil, err + } + if stderr.Len() != 0 { + return nil, fmt.Errorf("fmt completed successfully, but has output on stderr: %s", stderr.Bytes()) + } + if stdout.Len() == 0 { + return nil, fmt.Errorf("fmt completed successfully, but has no output on stdout") + } + return stdout.Bytes(), nil +} diff --git a/src/margo.sh/golang/completion.go b/src/margo.sh/golang/completion.go index 106ff412..ad4f6a47 100644 --- a/src/margo.sh/golang/completion.go +++ b/src/margo.sh/golang/completion.go @@ -1,9 +1,9 @@ package golang import ( - "margo.sh/mg" "go/ast" "go/token" + "margo.sh/mg" "strings" ) @@ -69,7 +69,8 @@ func NewCompletionCtx(mx *mg.Ctx, src []byte, pos int) *CompletionCtx { cx.IsTestFile = strings.HasSuffix(mx.View.Filename(), "_test.go") || strings.HasSuffix(cx.PkgName, "_test") - if cx.PkgName == "_" || cx.PkgName == "" { + if cx.PkgName == NilPkgName || cx.PkgName == "" { + cx.PkgName = NilPkgName cx.Scope |= PackageScope return cx } diff --git a/src/margo.sh/golang/gocode.go b/src/margo.sh/golang/gocode.go index 7e6a86b6..a5d1fbe6 100644 --- a/src/margo.sh/golang/gocode.go +++ b/src/margo.sh/golang/gocode.go @@ -2,9 +2,6 @@ package golang import ( "bytes" - "margo.sh/golang/internal/gocode" - "margo.sh/mg" - "margo.sh/sublime" "fmt" "go/ast" "go/build" @@ -12,6 +9,9 @@ import ( "go/printer" "go/token" "io" + "margo.sh/golang/internal/gocode" + "margo.sh/mg" + "margo.sh/sublime" "strings" "unicode" ) @@ -65,7 +65,7 @@ func (g *Gocode) funcTitle(fx *ast.FuncType, buf *bytes.Buffer, decl string) str if fx.Params != nil { switch { case g.ShowFuncParams: - g.printFields(buf, fset, fx.Params.List, true) + printFields(buf, fset, fx.Params.List, true) case fx.Params.NumFields() != 0: buf.WriteString("…") } @@ -78,7 +78,7 @@ func (g *Gocode) funcTitle(fx *ast.FuncType, buf *bytes.Buffer, decl string) str if hasNames { buf.WriteString("(") } - g.printFields(buf, fset, fl.List, g.ShowFuncResultNames) + printFields(buf, fset, fl.List, g.ShowFuncResultNames) if hasNames { buf.WriteString(")") } @@ -123,7 +123,7 @@ func (g *Gocode) funcSrc(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCand return buf.String() } -func (g *Gocode) printFields(w io.Writer, fset *token.FileSet, list []*ast.Field, printNames bool) { +func printFields(w io.Writer, fset *token.FileSet, list []*ast.Field, printNames bool) { for i, field := range list { if i > 0 { fmt.Fprint(w, ", ") diff --git a/src/margo.sh/golang/gocode_calltips.go b/src/margo.sh/golang/gocode_calltips.go new file mode 100644 index 00000000..65c3980f --- /dev/null +++ b/src/margo.sh/golang/gocode_calltips.go @@ -0,0 +1,181 @@ +package golang + +import ( + "bytes" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "margo.sh/golang/internal/gocode" + "margo.sh/mg" + "margo.sh/sublime" + "strings" +) + +type GocodeCalltips struct{} + +func (gc *GocodeCalltips) Reduce(mx *mg.Ctx) *mg.State { + st := mx.State + if !mx.LangIs("go") { + return st + } + if cfg, ok := st.Config.(sublime.Config); ok { + st = st.SetConfig(cfg.DisableCalltips()) + } + + src, _ := mx.View.ReadAll() + if len(src) == 0 { + return st + } + + type key struct{ hash string } + k := key{mg.SrcHash(src)} + + if mx.ActionIs(mg.ViewPosChanged{}, mg.ViewActivated{}) { + gc.process(mx, k, src) + } + + if s, _ := mx.Store.Get(k).(string); s != "" { + return st.AddStatus(s) + } + return st +} + +func (gc *GocodeCalltips) process(mx *mg.Ctx, key interface{}, src []byte) { + mx.Store.Del(key) + + srcPos := clampSrcPos(src, mx.View.Pos) + srcPos = mg.BytePos(src, srcPos) + cn := ParseCursorNode(nil, src, srcPos) + tpos := cn.TokenFile.Pos(srcPos) + var call *ast.CallExpr + for i := len(cn.Nodes) - 1; i >= 0; i-- { + x, ok := cn.Nodes[i].(*ast.CallExpr) + if !ok { + continue + } + + // we found a CallExpr, but it's not necessarily the right one. + // in `funcF(fun|cG())` this will match funcG, but we want funcF + // so we track of the first CallExpr but keep searching until we find one + // whose left paren is before the cursor + if call == nil { + call = x + } + if x.Lparen < tpos { + call = x + break + } + } + if call == nil { + return + } + + ident := gc.exprIdent(call.Fun) + if ident == nil { + return + } + + funcName := ident.String() + idPos := cn.TokenFile.Position(ident.End()).Offset + candidate, ok := gc.candidate(mx, src, idPos, funcName) + if !ok { + return + } + + x, _ := parser.ParseExpr(candidate.Type) + fx, _ := x.(*ast.FuncType) + if fx == nil { + return + } + + mx.Store.Put(key, gc.funcSrc(fx, gc.argPos(call, tpos), funcName)) +} + +func (gc *GocodeCalltips) funcSrc(fx *ast.FuncType, argPos int, funcName string) string { + fset := token.NewFileSet() + buf := &bytes.Buffer{} + buf.WriteString("func ") + buf.WriteString(funcName) + buf.WriteString("(") + fieldPos := 0 + for inField, field := range fx.Params.List { + for _, name := range field.Names { + if fieldPos > 0 { + buf.WriteString(", ") + } + fieldPos++ + + if inField == argPos { + buf.WriteString("⎨") + } + buf.WriteString(name.String()) + buf.WriteString(" ") + printer.Fprint(buf, fset, field.Type) + if inField == argPos { + buf.WriteString("⎬") + } + } + } + buf.WriteString(")") + + if fl := fx.Results; fl != nil { + buf.WriteString(" ") + hasNames := len(fl.List) != 0 && len(fl.List[0].Names) != 0 + if hasNames { + buf.WriteString("(") + } + printFields(buf, fset, fl.List, true) + if hasNames { + buf.WriteString(")") + } + } + + return buf.String() +} + +func (gc *GocodeCalltips) argPos(call *ast.CallExpr, tpos token.Pos) int { + np := token.NoPos + ne := token.NoPos + for i, a := range call.Args { + if np == token.NoPos { + np = a.Pos() + } + ne = a.End() + if np <= tpos && tpos <= ne { + return i + } + np = a.End() + 1 + } + return -1 +} + +func (gc *GocodeCalltips) candidate(mx *mg.Ctx, src []byte, pos int, funcName string) (candidate gocode.MargoCandidate, ok bool) { + if pos < 0 || pos >= len(src) { + return candidate, false + } + + bctx := BuildContext(mx) + candidates := gocode.Margo.Complete(gocode.MargoConfig{ + GOROOT: bctx.GOROOT, + GOPATHS: PathList(bctx.GOPATH), + UnimportedPackages: true, + }, src, mx.View.Filename(), pos) + + for _, c := range candidates { + if strings.HasPrefix(c.Type, "func(") && strings.EqualFold(funcName, c.Name) { + return c, true + } + } + return candidate, false +} + +func (gc *GocodeCalltips) exprIdent(x ast.Expr) *ast.Ident { + switch x := x.(type) { + case *ast.Ident: + return x + case *ast.SelectorExpr: + return x.Sel + } + return nil +} diff --git a/src/margo.sh/golang/gofmt.go b/src/margo.sh/golang/gofmt.go index bd4ae5e7..735c4404 100644 --- a/src/margo.sh/golang/gofmt.go +++ b/src/margo.sh/golang/gofmt.go @@ -1,66 +1,45 @@ package golang import ( - "bytes" - "fmt" "go/format" - "os/exec" - + mgformat "margo.sh/format" "margo.sh/mg" "margo.sh/sublime" ) var ( - GoFmt = FmtFunc(goFmt) - GoImports = FmtFunc(goImports) + GoFmt mg.Reducer = mg.Reduce(goFmt) + GoImports mg.Reducer = mg.Reduce(goImports) ) -type FmtFunc func(mx *mg.Ctx, src []byte) ([]byte, error) - -func (ff FmtFunc) Reduce(mx *mg.Ctx) *mg.State { - st := mx.State - if cfg, ok := mx.Config.(sublime.Config); ok { - st = st.SetConfig(cfg.DisableGsFmt()) - } - - if !mx.View.LangIs("go") { - return st - } - if !mx.ActionIs(mg.ViewFmt{}, mg.ViewPreSave{}) { - return st +func disableGsFmt(st *mg.State) *mg.State { + if cfg, ok := st.Config.(sublime.Config); ok { + return st.SetConfig(cfg.DisableGsFmt()) } + return st +} - fn := st.View.Filename() - src, err := st.View.ReadAll() - if err != nil { - return st.Errorf("failed to read %s: %s\n", fn, err) - } +type FmtFunc func(mx *mg.Ctx, src []byte) ([]byte, error) - src, err = ff(mx, src) - if err != nil { - return st.Errorf("failed to fmt %s: %s\n", fn, err) - } - return st.SetSrc(src) +func (ff FmtFunc) Reduce(mx *mg.Ctx) *mg.State { + return disableGsFmt(mgformat.FmtFunc{ + Fmt: ff, + Langs: []string{"go"}, + Actions: []mg.Action{mg.ViewPreSave{}}, + }.Reduce(mx)) } -func goFmt(_ *mg.Ctx, src []byte) ([]byte, error) { - return format.Source(src) +func goFmt(mx *mg.Ctx) *mg.State { + return FmtFunc(func(_ *mg.Ctx, src []byte) ([]byte, error) { + return format.Source(src) + }).Reduce(mx) } -func goImports(mx *mg.Ctx, src []byte) ([]byte, error) { - stdin := bytes.NewReader(src) - stdout := bytes.NewBuffer(nil) - stderr := bytes.NewBuffer(nil) - cmd := exec.Command("goimports", "-srcdir", mx.View.Filename()) - cmd.Env = mx.Env.Environ() - cmd.Stdin = stdin - cmd.Stdout = stdout - cmd.Stderr = stderr - if err := cmd.Run(); err != nil { - return nil, err - } - if stderr.Len() != 0 { - return nil, fmt.Errorf("fmt completed successfully, but contains stderr output: %s", stderr.Bytes()) - } - return stdout.Bytes(), nil +func goImports(mx *mg.Ctx) *mg.State { + return disableGsFmt(mgformat.FmtCmd{ + Name: "goimports", + Args: []string{"-srcdir", mx.View.Filename()}, + Langs: []string{"go"}, + Actions: []mg.Action{mg.ViewPreSave{}}, + }.Reduce(mx)) } diff --git a/src/margo.sh/golang/parse.go b/src/margo.sh/golang/parse.go index 25c55085..45548553 100644 --- a/src/margo.sh/golang/parse.go +++ b/src/margo.sh/golang/parse.go @@ -1,12 +1,12 @@ package golang import ( - "margo.sh/mg" "go/ast" "go/parser" "go/scanner" "go/token" "io/ioutil" + "margo.sh/mg" ) const ( @@ -14,8 +14,9 @@ const ( ) var ( + NilPkgName = "_" NilFset = token.NewFileSet() - NilAstFile, _ = parser.ParseFile(NilFset, "", `package _`, 0) + NilAstFile, _ = parser.ParseFile(NilFset, "", `package `+NilPkgName, 0) NilTokenFile = NilFset.File(NilAstFile.Pos()) ) diff --git a/src/margo.sh/golang/snippets.go b/src/margo.sh/golang/snippets.go index 25bb6210..482d7bca 100644 --- a/src/margo.sh/golang/snippets.go +++ b/src/margo.sh/golang/snippets.go @@ -1,8 +1,9 @@ package golang import ( - "margo.sh/mg" "go/ast" + "margo.sh/mg" + "regexp" "unicode" "unicode/utf8" ) @@ -18,6 +19,8 @@ var ( MapSnippet, TypeSnippet, } + + pkgDirNamePat = regexp.MustCompile(`(\w+)\W*$`) ) type SnippetFuncs []func(*CompletionCtx) []mg.Completion @@ -59,25 +62,37 @@ func (sf SnippetFuncs) fixCompletion(c *mg.Completion) { } func PackageNameSnippet(cx *CompletionCtx) []mg.Completion { - if cx.PkgName != "" || !cx.Scope.Is(PackageScope) { + if cx.PkgName != NilPkgName || !cx.Scope.Is(PackageScope) { return nil } - name := "main" - bx := BuildContext(cx.Ctx) - pkg, _ := bx.ImportDir(cx.View.Dir(), 0) - if pkg != nil && pkg.Name != "" { - name = pkg.Name + var cl []mg.Completion + seen := map[string]bool{} + add := func(name string) { + if seen[name] { + return + } + seen[name] = true + cl = append(cl, mg.Completion{ + Query: `package ` + name, + Src: ` + package ` + name + ` + + $0 + `, + }) } - return []mg.Completion{{ - Query: `package ` + name, - Src: ` - package ` + name + ` + dir := cx.View.Dir() + pkg, _ := BuildContext(cx.Ctx).ImportDir(dir, 0) + if pkg != nil && pkg.Name != "" { + add(pkg.Name) + } else { + add(pkgDirNamePat.FindString(dir)) + } + add("main") - $0 - `, - }} + return cl } func MainFuncSnippet(cx *CompletionCtx) []mg.Completion { diff --git a/src/margo.sh/mg/action.go b/src/margo.sh/mg/action.go index 7747c3e5..170253a3 100644 --- a/src/margo.sh/mg/action.go +++ b/src/margo.sh/mg/action.go @@ -8,7 +8,6 @@ var ( "Restart": func() Action { return Restart{} }, "Shutdown": func() Action { return Shutdown{} }, "ViewActivated": func() Action { return ViewActivated{} }, - "ViewClosed": func() Action { return ViewClosed{} }, "ViewFmt": func() Action { return ViewFmt{} }, "ViewLoaded": func() Action { return ViewLoaded{} }, "ViewModified": func() Action { return ViewModified{} }, @@ -60,5 +59,3 @@ type ViewPreSave struct{ ActionType } type ViewSaved struct{ ActionType } type ViewLoaded struct{ ActionType } - -type ViewClosed struct{ ActionType } diff --git a/src/margo.sh/mg/common.go b/src/margo.sh/mg/common.go index a467bbe8..253eb870 100644 --- a/src/margo.sh/mg/common.go +++ b/src/margo.sh/mg/common.go @@ -41,6 +41,21 @@ func (e EnvMap) Add(k, v string) EnvMap { return m } +func (e EnvMap) Merge(p map[string]string) EnvMap { + if len(p) == 0 { + return e + } + + m := make(EnvMap, len(e)+len(p)) + for k, v := range e { + m[k] = v + } + for k, v := range p { + m[k] = v + } + return m +} + func (e EnvMap) Environ() []string { l := make([]string, 0, len(e)) for k, v := range e { diff --git a/src/margo.sh/mg/store.go b/src/margo.sh/mg/store.go index cfce828e..36de6835 100644 --- a/src/margo.sh/mg/store.go +++ b/src/margo.sh/mg/store.go @@ -77,9 +77,10 @@ func (sto *Store) syncRq(ag *Agent, rq *agentReq) { sto.mu.Lock() defer sto.mu.Unlock() - rs := agentRes{Cookie: rq.Cookie} + rs := agentRes{Cookie: rq.Cookie, State: sto.state} for _, ra := range rq.Actions { st, err := sto.syncRqAct(ag, rq.Props, ra.Name) + sto.state = st // normally sto.updateState would do this rs.State = st if err != nil { rs.Error = err.Error() diff --git a/src/margo.sh/sublime/config.go b/src/margo.sh/sublime/config.go index 54d2a73d..5d6f4144 100644 --- a/src/margo.sh/sublime/config.go +++ b/src/margo.sh/sublime/config.go @@ -60,6 +60,10 @@ func (c Config) DisableGsComplete() Config { return c.overrideSetting("gscomplete_enabled", false) } +func (c Config) DisableCalltips() Config { + return c.overrideSetting("calltips", false) +} + func (c Config) DisableGsLint() Config { return c.overrideSetting("gslint_enabled", false) } diff --git a/src/margo.sh/vendor/github.com/ugorji/go/codec/decode.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/decode.go index 1c0817aa..c8be0bc5 100644 --- a/src/margo.sh/vendor/github.com/ugorji/go/codec/decode.go +++ b/src/margo.sh/vendor/github.com/ugorji/go/codec/decode.go @@ -2382,6 +2382,10 @@ func (d *Decoder) wrapErrstr(v interface{}, err *error) { *err = fmt.Errorf("%s decode error [pos %d]: %v", d.hh.Name(), d.r.numread(), v) } +func (d *Decoder) NumBytesRead() int { + return d.r.numread() +} + // -------------------------------------------------- // decSliceHelper assists when decoding into a slice, from a map or an array in the stream. diff --git a/src/margo.sh/vendor/github.com/ugorji/go/codec/gen.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/gen.go index 40fcf962..b4c4031f 100644 --- a/src/margo.sh/vendor/github.com/ugorji/go/codec/gen.go +++ b/src/margo.sh/vendor/github.com/ugorji/go/codec/gen.go @@ -542,7 +542,6 @@ func (x *genRunner) selfer(encode bool) { if encode { x.line(") CodecEncodeSelf(e *" + x.cpfx + "Encoder) {") x.genRequiredMethodVars(true) - // x.enc(genTopLevelVarName, t) x.encVar(genTopLevelVarName, t) } else { x.line(") CodecDecodeSelf(d *" + x.cpfx + "Decoder) {") @@ -649,7 +648,7 @@ func (x *genRunner) encVar(varname string, t reflect.Type) { case reflect.Ptr: telem := t.Elem() tek := telem.Kind() - if tek == reflect.Array || (tek == reflect.Struct && t != timeTyp) { + if tek == reflect.Array || (tek == reflect.Struct && telem != timeTyp) { x.enc(varname, genNonPtr(t)) break } diff --git a/src/margo.sh/vendor/github.com/ugorji/go/codec/helper_unsafe.go b/src/margo.sh/vendor/github.com/ugorji/go/codec/helper_unsafe.go index 1fb6d202..e3df60ab 100644 --- a/src/margo.sh/vendor/github.com/ugorji/go/codec/helper_unsafe.go +++ b/src/margo.sh/vendor/github.com/ugorji/go/codec/helper_unsafe.go @@ -157,7 +157,8 @@ func isEmptyValue(v reflect.Value, tinfos *TypeInfos, deref, checkStruct bool) b } return isnil case reflect.Ptr: - isnil := urv.ptr == nil + // isnil := urv.ptr == nil (not sufficient, as a pointer value encodes the type) + isnil := urv.ptr == nil || *(*unsafe.Pointer)(urv.ptr) == nil if deref { if isnil { return true From 63b2b031a0cd33e0c778fffdf7f16e225cc049d1 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sun, 8 Apr 2018 10:14:42 +0100 Subject: [PATCH 007/186] use the pkg snippets to generate content for new .go files --- gscommands.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gscommands.py b/gscommands.py index 90518880..2c0108ca 100644 --- a/gscommands.py +++ b/gscommands.py @@ -117,7 +117,7 @@ def run(self): gs.error_traceback('GsNewGoFile') self.window.new_file().run_command('gs_create_new_go_file', { - 'pkg_name': pkg_name, + 'pkg_name': '', 'file_name': 'main.go', }) @@ -126,9 +126,17 @@ def run(self, edit, pkg_name, file_name): view = self.view view.set_name(file_name) view.set_syntax_file(gs.tm_path('go')) - view.replace(edit, sublime.Region(0, view.size()), 'package %s\n' % pkg_name) - view.sel().clear() - view.sel().add(view.find(pkg_name, 0, sublime.LITERAL)) + if pkg_name == '': + view.sel().add(sublime.Region(0, 0)) + view.run_command('auto_complete', { + 'api_completions_only': True, + 'disable_auto_insert': True, + 'next_completion_if_showing': False, + }) + else: + view.replace(edit, sublime.Region(0, view.size()), 'package %s\n' % pkg_name) + view.sel().clear() + view.sel().add(view.find(pkg_name, 0, sublime.LITERAL)) class GsShowTasksCommand(sublime_plugin.WindowCommand): def run(self): From 41b3567ee3efa3da5d7b3a5d9dff15711b842072 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sun, 8 Apr 2018 10:15:43 +0100 Subject: [PATCH 008/186] if the view is None when draining the queued actions, do nothing --- gosubl/margo_agent.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gosubl/margo_agent.py b/gosubl/margo_agent.py index 1e3d101a..4fde2746 100644 --- a/gosubl/margo_agent.py +++ b/gosubl/margo_agent.py @@ -160,6 +160,9 @@ def _handle_send_ipc(self, rq): rq.done(AgentRes(error='Exception: %s' % exc, rq=rq, agent=self)) def _queued_acts(self, view): + if view is None: + return [] + with self._acts_lock: q, self._acts = self._acts, [] From 9348d945435d2bbe2b73b62ff49654ce6e2d84b1 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sun, 8 Apr 2018 10:25:14 +0100 Subject: [PATCH 009/186] don't send the content of non-source views --- gosubl/margo_state.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gosubl/margo_state.py b/gosubl/margo_state.py index 144f3b40..883da6b0 100644 --- a/gosubl/margo_state.py +++ b/gosubl/margo_state.py @@ -16,7 +16,6 @@ 'ViewPreSave', 'ViewSaved', 'ViewLoaded', - 'ViewClosed', )}) class Config(object): @@ -160,7 +159,7 @@ def _view_props(view): wd = view.settings().get('9o.wd') or wd props['Path'] = '_.9o' else: - src = _view_src(view) + src = _view_src(view, lang) props.update({ 'Wd': wd, @@ -229,13 +228,16 @@ def _view_scope_lang(view, pos): scope = view.scope_name(pos).strip().lower() l = _scope_lang_pat.findall(scope) - lang = l[-1] if l else scope.split('.')[-1] + lang = l[-1] if l else '' return (scope, lang) -def _view_src(view): +def _view_src(view, lang): if view is None: return '' + if not lang: + return '' + if not view.is_dirty(): return '' From 6187ee26420811b56373acfe6fb4e40168ecc802 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Tue, 10 Apr 2018 14:18:15 +0100 Subject: [PATCH 010/186] sync margo --- .../cmdpkg/margo/cmdrunner/cmdrunner.go | 18 +- src/margo.sh/golang/gocode_calltips.go | 6 +- src/margo.sh/mg/action.go | 66 ++++-- src/margo.sh/mg/agent.go | 36 ++-- src/margo.sh/mg/builtins.go | 126 +++++++++++ src/margo.sh/mg/client-actions.go | 4 +- src/margo.sh/mg/cmd.go | 195 ++++++++++++++++++ src/margo.sh/mg/cmd_posix.go | 13 ++ src/margo.sh/mg/reducers.go | 2 + src/margo.sh/mg/state.go | 64 ++++-- src/margo.sh/mg/store.go | 26 +-- src/margo.sh/mg/usercmds.go | 21 ++ src/margo.sh/mgutil/mgutil.go | 28 +++ 13 files changed, 525 insertions(+), 80 deletions(-) create mode 100644 src/margo.sh/mg/builtins.go create mode 100644 src/margo.sh/mg/cmd.go create mode 100644 src/margo.sh/mg/cmd_posix.go create mode 100644 src/margo.sh/mg/usercmds.go create mode 100644 src/margo.sh/mgutil/mgutil.go diff --git a/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go b/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go index fc45ba38..70cab9b6 100644 --- a/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go +++ b/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go @@ -2,9 +2,9 @@ package cmdrunner import ( "fmt" + "margo.sh/mgutil" "os" "os/exec" - "strconv" "strings" ) @@ -42,23 +42,9 @@ func (c Cmd) Run() error { a := append([]string{c.Name}, c.Args...) for i, s := range a { - a[i] = quoteFlag(s) + a[i] = mgutil.QuoteCmdArg(s) } fmt.Fprintf(os.Stderr, "``` %s ```\n", strings.Join(a, " ")) return cmd.Run() } - -func quoteFlag(s string) string { - eqPos := strings.Index(s, "=") - switch { - case s == "": - return `""` - case !strings.Contains(s, " "): - return s - case strings.HasPrefix(s, "-") && eqPos > 0: - return s[:eqPos+1] + strconv.Quote(s[eqPos+1:]) - default: - return strconv.Quote(s) - } -} diff --git a/src/margo.sh/golang/gocode_calltips.go b/src/margo.sh/golang/gocode_calltips.go index 65c3980f..3a16bac0 100644 --- a/src/margo.sh/golang/gocode_calltips.go +++ b/src/margo.sh/golang/gocode_calltips.go @@ -50,7 +50,11 @@ func (gc *GocodeCalltips) process(mx *mg.Ctx, key interface{}, src []byte) { tpos := cn.TokenFile.Pos(srcPos) var call *ast.CallExpr for i := len(cn.Nodes) - 1; i >= 0; i-- { - x, ok := cn.Nodes[i].(*ast.CallExpr) + nod := cn.Nodes[i] + if _, ok := nod.(*ast.BlockStmt); ok { + break + } + x, ok := nod.(*ast.CallExpr) if !ok { continue } diff --git a/src/margo.sh/mg/action.go b/src/margo.sh/mg/action.go index 170253a3..64b6ee72 100644 --- a/src/margo.sh/mg/action.go +++ b/src/margo.sh/mg/action.go @@ -1,32 +1,68 @@ package mg +import ( + "github.com/ugorji/go/codec" +) + var ( actionCreators = map[string]actionCreator{ - "QueryCompletions": func() Action { return QueryCompletions{} }, - "QueryIssues": func() Action { return QueryIssues{} }, - "QueryTooltips": func() Action { return QueryTooltips{} }, - "Restart": func() Action { return Restart{} }, - "Shutdown": func() Action { return Shutdown{} }, - "ViewActivated": func() Action { return ViewActivated{} }, - "ViewFmt": func() Action { return ViewFmt{} }, - "ViewLoaded": func() Action { return ViewLoaded{} }, - "ViewModified": func() Action { return ViewModified{} }, - "ViewPosChanged": func() Action { return ViewPosChanged{} }, - "ViewPreSave": func() Action { return ViewPreSave{} }, - "ViewSaved": func() Action { return ViewSaved{} }, + "QueryCompletions": func(codec.Handle, agentReqAction) (Action, error) { + return QueryCompletions{}, nil + }, + "QueryIssues": func(codec.Handle, agentReqAction) (Action, error) { + return QueryIssues{}, nil + }, + "QueryTooltips": func(codec.Handle, agentReqAction) (Action, error) { + return QueryTooltips{}, nil + }, + "Restart": func(codec.Handle, agentReqAction) (Action, error) { + return Restart{}, nil + }, + "Shutdown": func(codec.Handle, agentReqAction) (Action, error) { + return Shutdown{}, nil + }, + "ViewActivated": func(codec.Handle, agentReqAction) (Action, error) { + return ViewActivated{}, nil + }, + "ViewFmt": func(codec.Handle, agentReqAction) (Action, error) { + return ViewFmt{}, nil + }, + "ViewLoaded": func(codec.Handle, agentReqAction) (Action, error) { + return ViewLoaded{}, nil + }, + "ViewModified": func(codec.Handle, agentReqAction) (Action, error) { + return ViewModified{}, nil + }, + "ViewPosChanged": func(codec.Handle, agentReqAction) (Action, error) { + return ViewPosChanged{}, nil + }, + "ViewPreSave": func(codec.Handle, agentReqAction) (Action, error) { + return ViewPreSave{}, nil + }, + "ViewSaved": func(codec.Handle, agentReqAction) (Action, error) { + return ViewSaved{}, nil + }, + "RunCmd": func(h codec.Handle, a agentReqAction) (Action, error) { + act := RunCmd{} + err := codec.NewDecoderBytes(a.Data, h).Decode(&act) + return act, err + }, + "QueryUserCmds": func(h codec.Handle, a agentReqAction) (Action, error) { + return QueryUserCmds{}, nil + }, } ) -type actionCreator func() Action +type actionCreator func(codec.Handle, agentReqAction) (Action, error) type ActionType struct{} -func (act ActionType) Type() ActionType { +func (act ActionType) Action() ActionType { return act } type Action interface { - Type() ActionType + Action() ActionType } var Render Action = nil diff --git a/src/margo.sh/mg/agent.go b/src/margo.sh/mg/agent.go index bbe89685..75d50938 100644 --- a/src/margo.sh/mg/agent.go +++ b/src/margo.sh/mg/agent.go @@ -65,6 +65,7 @@ type AgentConfig struct { type agentReqAction struct { Name string + Data codec.Raw } type agentReq struct { @@ -88,31 +89,34 @@ type agentRes struct { } func (rs agentRes) finalize() interface{} { - v := struct { + out := struct { agentRes State struct { State Config interface{} - ClientActions []clientAction + ClientActions []clientActionType } }{} - v.agentRes = rs - v.State.State = *rs.State - v.State.ClientActions = rs.State.clientActions + out.agentRes = rs + out.State.State = *rs.State + inSt := &out.State.State + outSt := &out.State - if v.Error == "" { - v.Error = strings.Join([]string(v.State.Errors), "\n") + outSt.ClientActions = inSt.clientActions + + if out.Error == "" { + out.Error = strings.Join([]string(outSt.Errors), "\n") } - if v.State.View.changed == 0 { - v.State.View = nil + if outSt.View.changed == 0 { + outSt.View = nil } - if ec := rs.State.Config; ec != nil { - v.State.Config = ec.EditorConfig() + if ec := inSt.Config; ec != nil { + outSt.Config = ec.EditorConfig() } - return v + return out } type Agent struct { @@ -161,11 +165,11 @@ func (ag *Agent) communicate() error { return nil } -func (ag *Agent) createAction(name string) Action { - if f := actionCreators[name]; f != nil { - return f() +func (ag *Agent) createAction(ra agentReqAction, h codec.Handle) (Action, error) { + if f := actionCreators[ra.Name]; f != nil { + return f(h, ra) } - return nil + return nil, fmt.Errorf("Unknown action: %s", ra.Name) } func (ag *Agent) listener(st *State) { diff --git a/src/margo.sh/mg/builtins.go b/src/margo.sh/mg/builtins.go new file mode 100644 index 00000000..6d0104c9 --- /dev/null +++ b/src/margo.sh/mg/builtins.go @@ -0,0 +1,126 @@ +package mg + +import ( + "bytes" + "fmt" + "margo.sh/mgutil" + "os" + "sort" + "strings" +) + +var ( + Builtins = BuiltinCmds{} +) + +type BultinCmdList []BultinCmd + +// Lookup looks up the builtin command `name` in the list. +// If the command is not found, it returns `Builtins.Commands().Lookup(".exec")`. +// In either case, `found` indicates whether or not `name` was actually found. +func (bcl BultinCmdList) Lookup(name string) (cmd BultinCmd, found bool) { + for _, c := range bcl { + if c.Name == name { + return c, true + } + } + for _, c := range Builtins.Commands() { + if c.Name == ".exec" { + return c, false + } + } + panic("internal error: the `.exec` BuiltinCmd is not defined") +} + +type BuiltinCmds struct{} + +func (bc BuiltinCmds) ExecCmd(bx *BultinCmdCtx) *State { + rc := bx.RunCmd + if rc.Name == ".exec" { + if len(rc.Args) == 0 { + return bx.Done(nil) + } + rc.Name = rc.Args[0] + rc.Args = rc.Args[1:] + } + + p, err := starCmd(bx.Ctx, rc) + if err != nil { + a := append([]string{rc.Name}, rc.Args...) + for i, s := range a { + a[i] = mgutil.QuoteCmdArg(s) + } + return bx.Errorf("cannot exec `%s`: %s", strings.Join(a, " "), err) + } + + go p.Wait() + + return bx.State +} + +func (bc BuiltinCmds) TypeCmd(bx *BultinCmdCtx) *State { + cmds := bx.BuiltinCmds + names := bx.Args + if len(names) == 0 { + names = make([]string, len(cmds)) + for i, c := range cmds { + names[i] = c.Name + } + } + + buf := &bytes.Buffer{} + for _, name := range names { + c, _ := cmds.Lookup(name) + fmt.Fprintf(buf, "%s: builtin: %s, desc: %s\n", name, c.Name, c.Desc) + } + + return bx.Done(buf.Bytes()) +} + +func (bc BuiltinCmds) EnvCmd(bx *BultinCmdCtx) *State { + buf := &bytes.Buffer{} + names := bx.Args + if len(names) == 0 { + names = make([]string, 0, len(bx.Env)) + for k, _ := range bx.Env { + names = append(names, k) + } + sort.Strings(names) + } + for _, k := range names { + v := bx.Env.Get(k, os.Getenv(k)) + fmt.Fprintf(buf, "%s=%s\n", k, v) + } + return bx.Done(buf.Bytes()) +} + +func (bc BuiltinCmds) Commands() BultinCmdList { + return []BultinCmd{ + BultinCmd{Name: ".env", Desc: "List env vars", Run: bc.EnvCmd}, + BultinCmd{Name: ".exec", Desc: "Run a command through os/exec", Run: bc.ExecCmd}, + BultinCmd{Name: ".type", Desc: "Lists all builtins or which builtin handles a command", Run: bc.TypeCmd}, + } +} + +func (bc BuiltinCmds) Reduce(mx *Ctx) *State { + if _, ok := mx.Action.(RunCmd); ok { + return mx.State.AddBuiltinCmds(bc.Commands()...) + } + return mx.State +} + +type BultinCmdCtx struct { + *Ctx + RunCmd +} + +func (bx *BultinCmdCtx) Done(output []byte) *State { + bx.Store.Dispatch(CmdOutput{Fd: bx.Fd, Output: output, Close: true}) + return bx.State +} + +type BultinCmd struct { + Name string + Desc string + Run func(*BultinCmdCtx) *State +} diff --git a/src/margo.sh/mg/client-actions.go b/src/margo.sh/mg/client-actions.go index a0e495c5..591a54ff 100644 --- a/src/margo.sh/mg/client-actions.go +++ b/src/margo.sh/mg/client-actions.go @@ -6,7 +6,7 @@ var ( ) type clientAction interface { - Type() clientActionType + clientAction() clientActionType } type clientActionType struct { @@ -14,6 +14,6 @@ type clientActionType struct { Data interface{} } -func (t clientActionType) Type() clientActionType { +func (t clientActionType) clientAction() clientActionType { return t } diff --git a/src/margo.sh/mg/cmd.go b/src/margo.sh/mg/cmd.go new file mode 100644 index 00000000..7b4a0227 --- /dev/null +++ b/src/margo.sh/mg/cmd.go @@ -0,0 +1,195 @@ +package mg + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "sync" + "syscall" + "time" +) + +var ( + defaultSysProcAttr *syscall.SysProcAttr +) + +type CmdOutputWriter struct { + Fd string + + mu sync.Mutex + buf bytes.Buffer + closed bool +} + +func (w *CmdOutputWriter) Write(p []byte) (int, error) { + w.mu.Lock() + defer w.mu.Unlock() + + if w.closed { + return 0, os.ErrClosed + } + + return w.buf.Write(p) +} + +func (w *CmdOutputWriter) Close() error { + w.mu.Lock() + defer w.mu.Unlock() + + if w.closed { + return os.ErrClosed + } + w.closed = true + return nil +} + +func (w *CmdOutputWriter) Output() CmdOutput { + w.mu.Lock() + defer w.mu.Unlock() + + s := w.buf.Bytes() + w.buf.Reset() + return CmdOutput{Fd: w.Fd, Output: s, Close: w.closed} +} + +type CmdOutput struct { + ActionType + + Fd string + Output []byte + Close bool +} + +type cmdSupport struct{} + +func (cs *cmdSupport) Reduce(mx *Ctx) *State { + switch act := mx.Action.(type) { + case RunCmd: + return cs.runCmd(&BultinCmdCtx{Ctx: mx, RunCmd: act}) + case CmdOutput: + return cs.cmdOutput(mx, act) + } + return mx.State +} + +func (cs *cmdSupport) runCmd(bx *BultinCmdCtx) *State { + if cmd, ok := bx.BuiltinCmds.Lookup(bx.Name); ok { + return cmd.Run(bx) + } + return Builtins.ExecCmd(bx) +} + +func (cs *cmdSupport) cmdOutput(mx *Ctx, out CmdOutput) *State { + return mx.State.addClientActions(clientActionType{ + Name: "output", + Data: out, + }) +} + +type RunCmd struct { + ActionType + + Fd string + Input bool + Name string + Args []string +} + +type proc struct { + rc RunCmd + out *CmdOutputWriter + mu sync.RWMutex + done chan struct{} + mx *Ctx + cmd *exec.Cmd + task *TaskTicket +} + +func starCmd(mx *Ctx, rc RunCmd) (*proc, error) { + cmd := exec.Command(rc.Name, rc.Args...) + + if rc.Input { + r, err := mx.View.Open() + if err != nil { + return nil, err + } + cmd.Stdin = r + } + + out := &CmdOutputWriter{Fd: rc.Fd} + cmd.Dir = mx.View.Dir() + cmd.Env = mx.Env.Environ() + cmd.Stdout = out + cmd.Stderr = out + cmd.SysProcAttr = defaultSysProcAttr + + p := &proc{ + rc: rc, + done: make(chan struct{}), + mx: mx, + cmd: cmd, + out: out, + } + return p, p.start() +} + +func (p *proc) Cancel() { + p.mu.RLock() + defer p.mu.RUnlock() + + select { + case <-p.done: + default: + if p := p.cmd.Process; p != nil { + p.Signal(os.Interrupt) + } + } +} + +func (p *proc) start() error { + p.mu.Lock() + defer p.mu.Unlock() + + p.task = p.mx.Begin(Task{ + Title: fmt.Sprintf("RunCmd{Name: %s, Args: %s}", p.rc.Name, p.rc.Args), + Cancel: p.Cancel, + }) + go p.dispatchOutputLoop() + return p.cmd.Start() +} + +func (p *proc) dispatchOutput() { + p.mu.Lock() + defer p.mu.Unlock() + + out := p.out.Output() + if len(out.Output) != 0 || out.Close { + p.mx.Store.Dispatch(out) + } +} + +func (p *proc) dispatchOutputLoop() { + for { + select { + case <-p.done: + return + case <-time.After(1 * time.Second): + p.dispatchOutput() + } + } +} + +func (p *proc) Wait() error { + defer p.dispatchOutput() + defer func() { + p.mu.Lock() + defer p.mu.Unlock() + + close(p.done) + p.out.Close() + p.task.Done() + }() + + return p.cmd.Wait() +} diff --git a/src/margo.sh/mg/cmd_posix.go b/src/margo.sh/mg/cmd_posix.go new file mode 100644 index 00000000..3d2e8d6a --- /dev/null +++ b/src/margo.sh/mg/cmd_posix.go @@ -0,0 +1,13 @@ +// +build posix + +package mg + +import ( + "syscall" +) + +func init() { + defaultSysProcAttr = &syscall.SysProcAttr{ + Setsid: true, + } +} diff --git a/src/margo.sh/mg/reducers.go b/src/margo.sh/mg/reducers.go index 558854af..47dd64b2 100644 --- a/src/margo.sh/mg/reducers.go +++ b/src/margo.sh/mg/reducers.go @@ -13,9 +13,11 @@ var ( }{ before: []Reducer{ &restartSupport{}, + Builtins, }, after: []Reducer{ issueSupport{}, + &cmdSupport{}, }, } ) diff --git a/src/margo.sh/mg/state.go b/src/margo.sh/mg/state.go index 075abeda..e07452c7 100644 --- a/src/margo.sh/mg/state.go +++ b/src/margo.sh/mg/state.go @@ -197,30 +197,35 @@ type EditorConfig interface { EnabledForLangs(langs ...string) EditorConfig } -type EphemeralState struct { - Config EditorConfig - Status StrSet - Errors StrSet - Completions []Completion - Tooltips []Tooltip - Issues IssueSet -} - -type State struct { - EphemeralState +type stickyState struct { View *View Env EnvMap Editor EditorProps - - clientActions []clientAction } -func NewState() *State { +type State struct { + stickyState + Config EditorConfig + Status StrSet + Errors StrSet + Completions []Completion + Tooltips []Tooltip + Issues IssueSet + clientActions []clientActionType + BuiltinCmds BultinCmdList + UserCmds []UserCmd +} + +func newState() *State { return &State{ - View: newView(), + stickyState: stickyState{View: newView()}, } } +func (st *State) new() *State { + return &State{stickyState: st.stickyState} +} + func (st *State) Copy(updaters ...func(*State)) *State { x := *st for _, f := range updaters { @@ -292,10 +297,35 @@ func (st *State) AddIssues(l ...Issue) *State { }) } +func (st *State) AddBuiltinCmds(l ...BultinCmd) *State { + if len(l) == 0 { + return st + } + return st.Copy(func(st *State) { + st.BuiltinCmds = append(st.BuiltinCmds[:len(st.BuiltinCmds):len(st.BuiltinCmds)], l...) + }) +} + +func (st *State) AddUserCmds(l ...UserCmd) *State { + if len(l) == 0 { + return st + } + return st.Copy(func(st *State) { + st.UserCmds = append(st.UserCmds[:len(st.UserCmds):len(st.UserCmds)], l...) + }) +} + func (st *State) addClientActions(l ...clientAction) *State { + if len(l) == 0 { + return st + } return st.Copy(func(st *State) { - el := st.clientActions - st.clientActions = append(el[:len(el):len(el)], l...) + el := make([]clientActionType, 0, len(st.clientActions)+len(l)) + el = append(el, st.clientActions...) + for _, ca := range l { + el = append(el, ca.clientAction()) + } + st.clientActions = el }) } diff --git a/src/margo.sh/mg/store.go b/src/margo.sh/mg/store.go index 36de6835..86314afd 100644 --- a/src/margo.sh/mg/store.go +++ b/src/margo.sh/mg/store.go @@ -1,7 +1,6 @@ package mg import ( - "fmt" "sync" ) @@ -79,9 +78,11 @@ func (sto *Store) syncRq(ag *Agent, rq *agentReq) { rs := agentRes{Cookie: rq.Cookie, State: sto.state} for _, ra := range rq.Actions { - st, err := sto.syncRqAct(ag, rq.Props, ra.Name) - sto.state = st // normally sto.updateState would do this - rs.State = st + st, err := sto.syncRqAct(ag, rq.Props, ra) + if st != nil { + sto.state = st // normally sto.updateState would do this + rs.State = st + } if err != nil { rs.Error = err.Error() } @@ -90,13 +91,13 @@ func (sto *Store) syncRq(ag *Agent, rq *agentReq) { ag.send(rs) } -func (sto *Store) syncRqAct(ag *Agent, props clientProps, name string) (*State, error) { - mx, done := newCtx(sto.ag, sto.state, ag.createAction(name), sto) - defer close(done) - - if mx.Action == nil { - return mx.State, fmt.Errorf("unknown client action: %s", name) +func (sto *Store) syncRqAct(ag *Agent, props clientProps, ra agentReqAction) (*State, error) { + act, err := ag.createAction(ra, ag.handle) + if err != nil { + return nil, err } + mx, done := newCtx(sto.ag, sto.state, act, sto) + defer close(done) // TODO: add support for unpacking Action.Data @@ -125,8 +126,7 @@ func (sto *Store) State() *State { } func (sto *Store) prepState(st *State) *State { - st = st.Copy() - st.EphemeralState = EphemeralState{} + st = st.new() if sto.cfg != nil { st.Config = sto.cfg } @@ -137,7 +137,7 @@ func newStore(ag *Agent, l Listener) *Store { sto := &Store{ readyCh: make(chan struct{}), listener: l, - state: NewState(), + state: newState(), ag: ag, } sto.cache.m = map[interface{}]interface{}{} diff --git a/src/margo.sh/mg/usercmds.go b/src/margo.sh/mg/usercmds.go new file mode 100644 index 00000000..c9ff575c --- /dev/null +++ b/src/margo.sh/mg/usercmds.go @@ -0,0 +1,21 @@ +package mg + +// QueryUserCmds is the action dispatched to get a list of UserCmds. +type QueryUserCmds struct { + ActionType +} + +// UserCmd represents a command that may be displayed in the editor ui. +type UserCmd struct { + // Title is the name of the command displayed to the user + Title string + + // Desc describes what the command invocation does + Desc string + + // Name is the name of the command to run + Name string + + // Args is a list of args to pass to the command + Args []string +} diff --git a/src/margo.sh/mgutil/mgutil.go b/src/margo.sh/mgutil/mgutil.go new file mode 100644 index 00000000..48035788 --- /dev/null +++ b/src/margo.sh/mgutil/mgutil.go @@ -0,0 +1,28 @@ +// mgutil is a collections of utility types and functions with no dependency on margo.sh/mg +package mgutil + +import ( + "strconv" + "strings" +) + +// QuoteFlag uses strconv.Quote to quote the command arg s. +// NOTE: It's intended for use in command display and should not be used for shell security. +// e.g. +// `a b c` -> `"a b c"` +// `abc` -> `abc` +// `-abc=123` -> `-abc=123` +// `-abc=1 2 3` -> `-abc="1 2 3"` +func QuoteCmdArg(s string) string { + eqPos := strings.Index(s, "=") + switch { + case s == "": + return `""` + case !strings.Contains(s, " "): + return s + case strings.HasPrefix(s, "-") && eqPos > 0: + return s[:eqPos+1] + strconv.Quote(s[eqPos+1:]) + default: + return strconv.Quote(s) + } +} From 78dc48d2b4bc108c142390bed6ad3bf842207066 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Wed, 11 Apr 2018 10:51:54 +0100 Subject: [PATCH 011/186] sync margo --- .../cmdpkg/margo/cmdrunner/cmdrunner.go | 6 +- src/margo.sh/golang/gocmd.go | 77 +++++++++++++ src/margo.sh/mg/builtins.go | 106 ++++++++++++++---- src/margo.sh/mg/cmd.go | 70 +++++------- src/margo.sh/mg/{cmd_posix.go => cmd_nix.go} | 2 +- src/margo.sh/mg/issue.go | 79 ++++++++++++- src/margo.sh/mg/reducers.go | 7 +- src/margo.sh/mgutil/mgutil.go | 14 ++- 8 files changed, 279 insertions(+), 82 deletions(-) create mode 100644 src/margo.sh/golang/gocmd.go rename src/margo.sh/mg/{cmd_posix.go => cmd_nix.go} (85%) diff --git a/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go b/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go index 70cab9b6..e401916d 100644 --- a/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go +++ b/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go @@ -40,11 +40,7 @@ func (c Cmd) Run() error { } } - a := append([]string{c.Name}, c.Args...) - for i, s := range a { - a[i] = mgutil.QuoteCmdArg(s) - } - fmt.Fprintf(os.Stderr, "``` %s ```\n", strings.Join(a, " ")) + fmt.Fprintf(os.Stderr, "``` %s ```\n", mgutil.QuoteCmd(c.Name, c.Args...)) return cmd.Run() } diff --git a/src/margo.sh/golang/gocmd.go b/src/margo.sh/golang/gocmd.go new file mode 100644 index 00000000..30cbf9fa --- /dev/null +++ b/src/margo.sh/golang/gocmd.go @@ -0,0 +1,77 @@ +package golang + +import ( + "fmt" + "margo.sh/mg" + "strings" +) + +type GoCmd struct{} + +func (gc *GoCmd) Reduce(mx *mg.Ctx) *mg.State { + switch act := mx.Action.(type) { + case mg.RunCmd: + return gc.runCmd(mx, act) + } + return mx.State +} + +func (gc *GoCmd) runCmd(mx *mg.Ctx, rc mg.RunCmd) *mg.State { + return mx.State.AddBuiltinCmds( + mg.BultinCmd{Run: gc.gotoolCmd, Name: "go", Desc: "" + + "Wrapper around the go command." + + " It adds `go .play` and `go .replay` as well as add linter support." + + "", + }, + ) +} + +func (gc *GoCmd) subCmd(args []string) string { + for _, s := range args { + if s != "" && !strings.HasPrefix(s, "-") { + return s + } + } + return "" +} + +func (gc *GoCmd) gotoolCmd(bx *mg.BultinCmdCtx) *mg.State { + go gc.gotool(bx) + return bx.State +} + +func (gc *GoCmd) gotool(bx *mg.BultinCmdCtx) { + defer bx.Close() + + subCmd := gc.subCmd(bx.Args) + dir := bx.View.Dir() + // TODO: detect pkgDir passed os args + pkgDir := dir + type Key struct{ subCmd, pkgDir string } + key := Key{subCmd, pkgDir} + + iw := &mg.IssueWriter{ + Patterns: CommonPatterns, + Base: mg.Issue{Label: strings.TrimSpace("go " + subCmd)}, + Dir: dir, + } + + bx = bx.Copy(func(bx *mg.BultinCmdCtx) { + bx.Output = &mg.CmdOutputWriter{ + Fd: bx.Output.Fd, + Writer: iw, + } + }) + + err := bx.RunProc() + if err != nil { + fmt.Fprintln(bx.Output, err) + return + } + iw.Flush() + + bx.Store.Dispatch(mg.StoreIssues{ + Key: mg.IssueKey{Key: key, Dir: pkgDir}, + Issues: iw.Issues(), + }) +} diff --git a/src/margo.sh/mg/builtins.go b/src/margo.sh/mg/builtins.go index 6d0104c9..2e3bdcd1 100644 --- a/src/margo.sh/mg/builtins.go +++ b/src/margo.sh/mg/builtins.go @@ -5,8 +5,8 @@ import ( "fmt" "margo.sh/mgutil" "os" + "os/exec" "sort" - "strings" ) var ( @@ -35,27 +35,28 @@ func (bcl BultinCmdList) Lookup(name string) (cmd BultinCmd, found bool) { type BuiltinCmds struct{} func (bc BuiltinCmds) ExecCmd(bx *BultinCmdCtx) *State { - rc := bx.RunCmd - if rc.Name == ".exec" { - if len(rc.Args) == 0 { - return bx.Done(nil) - } - rc.Name = rc.Args[0] - rc.Args = rc.Args[1:] - } + go bc.execCmd(bx) + return bx.State +} - p, err := starCmd(bx.Ctx, rc) - if err != nil { - a := append([]string{rc.Name}, rc.Args...) - for i, s := range a { - a[i] = mgutil.QuoteCmdArg(s) +func (bc BuiltinCmds) execCmd(bx *BultinCmdCtx) { + defer bx.Close() + + if bx.Name == ".exec" { + if len(bx.Args) == 0 { + return } - return bx.Errorf("cannot exec `%s`: %s", strings.Join(a, " "), err) + bx = bx.Copy(func(bx *BultinCmdCtx) { + bx.Name = bx.Args[0] + bx.Args = bx.Args[1:] + }) } - go p.Wait() - - return bx.State + err := bx.RunProc() + if err == nil { + return + } + fmt.Fprintf(bx.Output, "cannot exec `%s`: %s", mgutil.QuoteCmd(bx.Name, bx.Args...), err) } func (bc BuiltinCmds) TypeCmd(bx *BultinCmdCtx) *State { @@ -74,7 +75,8 @@ func (bc BuiltinCmds) TypeCmd(bx *BultinCmdCtx) *State { fmt.Fprintf(buf, "%s: builtin: %s, desc: %s\n", name, c.Name, c.Desc) } - return bx.Done(buf.Bytes()) + bx.Close(buf.Bytes()) + return bx.State } func (bc BuiltinCmds) EnvCmd(bx *BultinCmdCtx) *State { @@ -91,7 +93,8 @@ func (bc BuiltinCmds) EnvCmd(bx *BultinCmdCtx) *State { v := bx.Env.Get(k, os.Getenv(k)) fmt.Fprintf(buf, "%s=%s\n", k, v) } - return bx.Done(buf.Bytes()) + bx.Close(buf.Bytes()) + return bx.State } func (bc BuiltinCmds) Commands() BultinCmdList { @@ -112,11 +115,68 @@ func (bc BuiltinCmds) Reduce(mx *Ctx) *State { type BultinCmdCtx struct { *Ctx RunCmd + Output *CmdOutputWriter } -func (bx *BultinCmdCtx) Done(output []byte) *State { - bx.Store.Dispatch(CmdOutput{Fd: bx.Fd, Output: output, Close: true}) - return bx.State +func NewBultinCmdCtx(mx *Ctx, rc RunCmd) *BultinCmdCtx { + return &BultinCmdCtx{ + Ctx: mx, + RunCmd: rc, + Output: &CmdOutputWriter{Fd: rc.Fd}, + } +} + +func (bx *BultinCmdCtx) update(updaters ...func(*BultinCmdCtx)) *BultinCmdCtx { + for _, f := range updaters { + f(bx) + } + return bx +} + +func (bx *BultinCmdCtx) Copy(updaters ...func(*BultinCmdCtx)) *BultinCmdCtx { + x := *bx + return x.update(updaters...) +} + +func (bx *BultinCmdCtx) Close(output ...[]byte) { + for _, s := range output { + bx.Output.Write(s) + } + bx.Output.Close() + bx.Store.Dispatch(bx.Output.Output()) +} + +func (bx *BultinCmdCtx) RunProc() error { + p, err := bx.StartProc() + if err != nil { + return err + } + return p.Wait() +} + +func (bx *BultinCmdCtx) StartProc() (*Proc, error) { + cmd := exec.Command(bx.Name, bx.Args...) + + if bx.Input { + r, err := bx.View.Open() + if err != nil { + return nil, err + } + cmd.Stdin = r + } + + cmd.Dir = bx.View.Dir() + cmd.Env = bx.Env.Environ() + cmd.Stdout = bx.Output + cmd.Stderr = bx.Output + cmd.SysProcAttr = defaultSysProcAttr + + p := &Proc{ + done: make(chan struct{}), + bx: bx, + cmd: cmd, + } + return p, p.start() } type BultinCmd struct { diff --git a/src/margo.sh/mg/cmd.go b/src/margo.sh/mg/cmd.go index 7b4a0227..e6c28889 100644 --- a/src/margo.sh/mg/cmd.go +++ b/src/margo.sh/mg/cmd.go @@ -3,6 +3,8 @@ package mg import ( "bytes" "fmt" + "io" + "margo.sh/mgutil" "os" "os/exec" "sync" @@ -15,6 +17,8 @@ var ( ) type CmdOutputWriter struct { + io.Writer + io.Closer Fd string mu sync.Mutex @@ -30,7 +34,11 @@ func (w *CmdOutputWriter) Write(p []byte) (int, error) { return 0, os.ErrClosed } - return w.buf.Write(p) + n, err := w.buf.Write(p) + if w.Writer != nil { + return w.Writer.Write(p) + } + return n, err } func (w *CmdOutputWriter) Close() error { @@ -40,7 +48,11 @@ func (w *CmdOutputWriter) Close() error { if w.closed { return os.ErrClosed } + w.closed = true + if w.Closer != nil { + return w.Closer.Close() + } return nil } @@ -66,7 +78,7 @@ type cmdSupport struct{} func (cs *cmdSupport) Reduce(mx *Ctx) *State { switch act := mx.Action.(type) { case RunCmd: - return cs.runCmd(&BultinCmdCtx{Ctx: mx, RunCmd: act}) + return cs.runCmd(NewBultinCmdCtx(mx, act)) case CmdOutput: return cs.cmdOutput(mx, act) } @@ -96,45 +108,15 @@ type RunCmd struct { Args []string } -type proc struct { - rc RunCmd - out *CmdOutputWriter +type Proc struct { + bx *BultinCmdCtx mu sync.RWMutex done chan struct{} - mx *Ctx cmd *exec.Cmd task *TaskTicket } -func starCmd(mx *Ctx, rc RunCmd) (*proc, error) { - cmd := exec.Command(rc.Name, rc.Args...) - - if rc.Input { - r, err := mx.View.Open() - if err != nil { - return nil, err - } - cmd.Stdin = r - } - - out := &CmdOutputWriter{Fd: rc.Fd} - cmd.Dir = mx.View.Dir() - cmd.Env = mx.Env.Environ() - cmd.Stdout = out - cmd.Stderr = out - cmd.SysProcAttr = defaultSysProcAttr - - p := &proc{ - rc: rc, - done: make(chan struct{}), - mx: mx, - cmd: cmd, - out: out, - } - return p, p.start() -} - -func (p *proc) Cancel() { +func (p *Proc) Cancel() { p.mu.RLock() defer p.mu.RUnlock() @@ -147,29 +129,29 @@ func (p *proc) Cancel() { } } -func (p *proc) start() error { +func (p *Proc) start() error { p.mu.Lock() defer p.mu.Unlock() - p.task = p.mx.Begin(Task{ - Title: fmt.Sprintf("RunCmd{Name: %s, Args: %s}", p.rc.Name, p.rc.Args), + p.task = p.bx.Begin(Task{ + Title: fmt.Sprintf("RunCmd `%s`", mgutil.QuoteCmd(p.bx.Name, p.bx.Args...)), Cancel: p.Cancel, }) go p.dispatchOutputLoop() return p.cmd.Start() } -func (p *proc) dispatchOutput() { +func (p *Proc) dispatchOutput() { p.mu.Lock() defer p.mu.Unlock() - out := p.out.Output() + out := p.bx.Output.Output() if len(out.Output) != 0 || out.Close { - p.mx.Store.Dispatch(out) + p.bx.Store.Dispatch(out) } } -func (p *proc) dispatchOutputLoop() { +func (p *Proc) dispatchOutputLoop() { for { select { case <-p.done: @@ -180,14 +162,14 @@ func (p *proc) dispatchOutputLoop() { } } -func (p *proc) Wait() error { +func (p *Proc) Wait() error { defer p.dispatchOutput() defer func() { p.mu.Lock() defer p.mu.Unlock() close(p.done) - p.out.Close() + p.bx.Output.Close() p.task.Done() }() diff --git a/src/margo.sh/mg/cmd_posix.go b/src/margo.sh/mg/cmd_nix.go similarity index 85% rename from src/margo.sh/mg/cmd_posix.go rename to src/margo.sh/mg/cmd_nix.go index 3d2e8d6a..37ae84d9 100644 --- a/src/margo.sh/mg/cmd_posix.go +++ b/src/margo.sh/mg/cmd_nix.go @@ -1,4 +1,4 @@ -// +build posix +// +build !windows package mg diff --git a/src/margo.sh/mg/issue.go b/src/margo.sh/mg/issue.go index 07ea1528..64063e30 100644 --- a/src/margo.sh/mg/issue.go +++ b/src/margo.sh/mg/issue.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "os" "path/filepath" "regexp" "strconv" @@ -113,9 +114,53 @@ func (is IssueSet) AllInView(v *View) IssueSet { return issues } -type issueSupport struct{} +type StoreIssues struct { + ActionType -func (_ issueSupport) Reduce(mx *Ctx) *State { + Key IssueKey + Issues IssueSet +} + +type IssueKey struct { + Key interface{} + Name string + Path string + Dir string +} + +type issueKeySupport struct { + issues map[IssueKey]IssueSet +} + +func (iks *issueKeySupport) Reduce(mx *Ctx) *State { + switch act := mx.Action.(type) { + case Started: + iks.issues = map[IssueKey]IssueSet{} + case StoreIssues: + if len(act.Issues) == 0 { + delete(iks.issues, act.Key) + } else { + iks.issues[act.Key] = act.Issues + } + } + + issues := IssueSet{} + clean := filepath.Clean + name := clean(mx.View.Name) + path := clean(mx.View.Path) + dir := clean(mx.View.Dir()) + for k, v := range iks.issues { + if k.Name == name || clean(k.Path) == path || clean(k.Dir) == dir { + issues = issues.Add(v...) + } + } + + return mx.State.AddIssues(issues...) +} + +type issueStatusSupport struct{} + +func (_ issueStatusSupport) Reduce(mx *Ctx) *State { if len(mx.Issues) == 0 { return mx.State } @@ -141,7 +186,8 @@ func (_ issueSupport) Reduce(mx *Ctx) *State { } type IssueWriter struct { - Writer io.Writer + io.Writer + io.Closer Patterns []*regexp.Regexp Base Issue Dir string @@ -151,12 +197,17 @@ type IssueWriter struct { issues IssueSet isu *Issue pfx []byte + closed bool } func (w *IssueWriter) Write(p []byte) (n int, err error) { w.mu.Lock() defer w.mu.Unlock() + if w.closed { + return 0, os.ErrClosed + } + w.buf = append(w.buf, p...) w.scan(false) @@ -166,6 +217,23 @@ func (w *IssueWriter) Write(p []byte) (n int, err error) { return len(p), nil } +func (w *IssueWriter) Close() error { + w.mu.Lock() + defer w.mu.Unlock() + + if w.closed { + return os.ErrClosed + } + + w.closed = true + w.flush() + + if w.Closer != nil { + return w.Closer.Close() + } + return nil +} + func (w *IssueWriter) Flush() error { w.mu.Lock() defer w.mu.Unlock() @@ -265,7 +333,10 @@ func (w *IssueWriter) matchOne(p *regexp.Regexp, s []byte) *Issue { case "end": isu.End = num(v) case "label": - isu.Label = str(v) + lbl := str(v) + if lbl != "" { + isu.Label = lbl + } case "error", "warning": isu.Tag = IssueTag(k) isu.Message = str(v) diff --git a/src/margo.sh/mg/reducers.go b/src/margo.sh/mg/reducers.go index 47dd64b2..127d0eab 100644 --- a/src/margo.sh/mg/reducers.go +++ b/src/margo.sh/mg/reducers.go @@ -12,12 +12,13 @@ var ( before, use, after []Reducer }{ before: []Reducer{ - &restartSupport{}, + &issueKeySupport{}, Builtins, }, after: []Reducer{ - issueSupport{}, + issueStatusSupport{}, &cmdSupport{}, + &restartSupport{}, }, } ) @@ -89,7 +90,7 @@ func (_ restartSupport) prepRestart(mx *Ctx) { mx.Log.Println(msg) mx.Store.Dispatch(Restart{}) } else { - mx.Log.Printf("not %s: go test failed: %s\n%s\n", msg, err, out) + mx.Log.Printf("not %s: `margo.sh build %s` failed: error: %v\n%s\n", msg, mx.AgentName(), err, out) mx.Store.Dispatch(res) } } diff --git a/src/margo.sh/mgutil/mgutil.go b/src/margo.sh/mgutil/mgutil.go index 48035788..b7a5372a 100644 --- a/src/margo.sh/mgutil/mgutil.go +++ b/src/margo.sh/mgutil/mgutil.go @@ -6,8 +6,8 @@ import ( "strings" ) -// QuoteFlag uses strconv.Quote to quote the command arg s. -// NOTE: It's intended for use in command display and should not be used for shell security. +// QuoteCmdArg uses strconv.Quote to quote the command arg s. +// NOTE: the result is for display only, and should not be used for shell security. // e.g. // `a b c` -> `"a b c"` // `abc` -> `abc` @@ -26,3 +26,13 @@ func QuoteCmdArg(s string) string { return strconv.Quote(s) } } + +// QuoteCmd joins `name [args]` with name and each arg quoted with QuoteCmdArg +// NOTE: the result is for display only, and should not be used for shell security. +func QuoteCmd(name string, args ...string) string { + a := append([]string{name}, args...) + for i, s := range a { + a[i] = QuoteCmdArg(s) + } + return strings.Join(a, " ") +} From cb4428bb79214d586cd49b1a438dfc2e3e4091aa Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Wed, 11 Apr 2018 11:01:06 +0100 Subject: [PATCH 012/186] add support for user-commands and run commands through margo for live command output * margo's `go` command is incomplete so replay, etc. doesn't work * change default 9o_instance to "9o" to get a single instance of 9o by default * add user commands with new key binding ctrl+., ctrl+c * fix .e key bindng on Mac and Windows. it should now call the margo command * add Packages/User/GoSublime/bin and Packages/GoSublime/bin to PATH --- Default (Linux).sublime-keymap | 4 + Default (OSX).sublime-keymap | 5 +- Default (Windows).sublime-keymap | 5 +- GoSublime.sublime-commands | 4 + GoSublime.sublime-settings | 4 +- gosubl/margo.py | 28 +++-- gosubl/margo_agent.py | 4 +- gosubl/margo_state.py | 33 ++++- gosubl/margo_sublime.py | 35 ++++++ gosubl/sh.py | 9 +- gs9o.py | 210 +++++++++++++++++++------------ 11 files changed, 245 insertions(+), 96 deletions(-) diff --git a/Default (Linux).sublime-keymap b/Default (Linux).sublime-keymap index 24fae8e6..dcbd61c3 100644 --- a/Default (Linux).sublime-keymap +++ b/Default (Linux).sublime-keymap @@ -49,6 +49,10 @@ "keys": ["ctrl+.", "ctrl+e"], "command": "margo_issues", }, + { + "keys": ["ctrl+.", "ctrl+c"], + "command": "margo_user_cmds", + }, { "keys": ["ctrl+.", "ctrl+["], "command": "gs_palette", diff --git a/Default (OSX).sublime-keymap b/Default (OSX).sublime-keymap index 3b42fab6..590e7091 100644 --- a/Default (OSX).sublime-keymap +++ b/Default (OSX).sublime-keymap @@ -41,9 +41,12 @@ }, { "keys": ["super+.", "super+e"], - "command": "gs_palette", "command": "margo_issues", }, + { + "keys": ["super+.", "super+c"], + "command": "margo_user_cmds", + }, { "keys": ["super+.", "super+["], "command": "gs_palette", diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap index 6f27e236..532ec807 100644 --- a/Default (Windows).sublime-keymap +++ b/Default (Windows).sublime-keymap @@ -47,9 +47,12 @@ }, { "keys": ["ctrl+.", "ctrl+e"], - "command": "gs_palette", "command": "margo_issues", }, + { + "keys": ["ctrl+.", "ctrl+c"], + "command": "margo_user_cmds", + }, { "keys": ["ctrl+.", "ctrl+["], "command": "gs_palette", diff --git a/GoSublime.sublime-commands b/GoSublime.sublime-commands index 8969f5c4..93d66c38 100644 --- a/GoSublime.sublime-commands +++ b/GoSublime.sublime-commands @@ -26,6 +26,10 @@ "caption": "GoSublime: Show Issues", "command": "margo_issues", }, + { + "caption": "GoSublime: User Commands", + "command": "margo_user_cmds", + }, { "caption": "GoSublime: Go to last bookmark", "command": "gs_palette", diff --git a/GoSublime.sublime-settings b/GoSublime.sublime-settings index 0fd80764..23d1862a 100644 --- a/GoSublime.sublime-settings +++ b/GoSublime.sublime-settings @@ -280,7 +280,9 @@ // if set, 9o will run in single-instance mode instead of per-pkg // the name can be any string, so you can e.g. set it per-project and maintain project-specific // command history - "9o_instance": "", + // + // setting it to the string "auto" will automatically assign it a name based on the current directory/package + "9o_instance": "9o", // if set 9o will use the specified color scheme. // the path must relative to `Packages` e.g. `Packages/My/9o Specific.tmTheme` diff --git a/gosubl/margo.py b/gosubl/margo.py index d82f03ef..96b847b0 100644 --- a/gosubl/margo.py +++ b/gosubl/margo.py @@ -15,10 +15,12 @@ def __init__(self): self.package_dir = os.path.dirname(os.path.abspath(__file__)) self.out = OutputLogger('margo') self.agent_tokens = TokenCounter('agent', format='{}#{:03d}', start=6) + self.run_tokens = TokenCounter('9o.run') self.agent = None self.enabled_for_langs = [] self.state = State() self.status = [] + self.output_handler = None def render(self, rs=None): if rs: @@ -34,17 +36,29 @@ def render(self, rs=None): if rs: if rs.agent is self.agent: - sublime.set_timeout_async(lambda: self._handle_client_actions(rs.state.client_actions), 0) + sublime.set_timeout_async(lambda: self._handle_client_actions(rs), 0) if rs.agent and rs.agent is not self.agent: rs.agent.stop() - def _handle_client_actions(self, client_actions): - for a in client_actions: - if a.name == 'restart': - self.restart() - elif a.name == 'shutdown': - self.stop() + def _handle_act_restart(self, rs, act): + self.restart() + + def _handle_act_shutdown(self, rs, act): + self.stop() + + def _handle_act_output(self, rs, act): + h = self.output_handler + if h: + h(rs, act) + + def _handle_client_actions(self, rs): + for a in rs.state.client_actions: + try: + f = getattr(self, '_handle_act_' + a.name) + f(rs, a) + except AttributeError: + self.out.println('Unknown client-action: %s: %s' % (a.name, a)) def render_status(self, *a): self.status = list(a) diff --git a/gosubl/margo_agent.py b/gosubl/margo_agent.py index 4fde2746..276bcd1d 100644 --- a/gosubl/margo_agent.py +++ b/gosubl/margo_agent.py @@ -14,12 +14,12 @@ from .vendor import umsgpack ipc_dec = umsgpack.load ipc_enc = umsgpack.dump - ipc_ignore_exceptions = (umsgpack.InsufficientDataException, BrokenPipeError) + ipc_ignore_exceptions = (umsgpack.InsufficientDataException, BrokenPipeError, ValueError) elif ipc_codec == 'cbor': from .vendor.cbor_py import cbor ipc_dec = cbor.load ipc_enc = cbor.dump - ipc_ignore_exceptions = (BrokenPipeError) + ipc_ignore_exceptions = (BrokenPipeError, ValueError) else: raise Exception('impossibru') diff --git a/gosubl/margo_state.py b/gosubl/margo_state.py index 883da6b0..1dd312fa 100644 --- a/gosubl/margo_state.py +++ b/gosubl/margo_state.py @@ -9,6 +9,7 @@ 'QueryCompletions', 'QueryTooltips', 'QueryIssues', + 'QueryUserCmds', 'ViewActivated', 'ViewModified', 'ViewPosChanged', @@ -16,6 +17,7 @@ 'ViewPreSave', 'ViewSaved', 'ViewLoaded', + 'RunCmd', )}) class Config(object): @@ -38,10 +40,17 @@ def __init__(self, v={}): self.config = Config(v.get('Config') or {}) self.status = v.get('Status') or [] self.view = ResView(v=v.get('View') or {}) - self.client_actions = [ClientAction(v=a) for a in (v.get('ClientActions') or [])] self.completions = [Completion(c) for c in (v.get('Completions') or [])] self.tooltips = [Tooltip(t) for t in (v.get('Tooltips') or [])] self.issues = [Issue(l) for l in (v.get('Issues') or [])] + self.user_cmds = [UserCmd(c) for c in (v.get('UserCmds') or [])] + + self.client_actions = [] + for ca in (v.get('ClientActions') or []): + if ca.get('Name') == 'output': + self.client_actions.append(CmdOutput(v=ca)) + else: + self.client_actions.append(ClientAction(v=ca)) def __repr__(self): return repr(self.__dict__) @@ -49,6 +58,21 @@ def __repr__(self): class ClientAction(object): def __init__(self, v={}): self.name = v.get('Name') or '' + self.data = v.get('Data') or {} + + def __repr__(self): + return repr(vars(self)) + +class CmdOutput(ClientAction): + def __init__(self, v={}): + super().__init__(v=v) + self.fd = self.data.get('Fd') or '' + self.output = self.data.get('Output') or '' + self.close = self.data.get('Close') or False + self.fd = self.data.get('Fd') or '' + + def __repr__(self): + return repr(vars(self)) class Completion(object): def __init__(self, v): @@ -104,6 +128,13 @@ def __init__(self, v={}): if isinstance(self.src, bytes): self.src = self.src.decode('utf-8') +class UserCmd(object): + def __init__(self, v={}): + self.title = v.get('Title') or '' + self.desc = v.get('Desc') or '' + self.name = v.get('Name') or '' + self.args = v.get('Args') or [] + # in testing, we should be able to push 50MiB+ files constantly without noticing a performance problem # but keep this number low (realistic source files sizes) at least until we optimize things MAX_VIEW_SIZE = 512 << 10 diff --git a/gosubl/margo_sublime.py b/gosubl/margo_sublime.py index 14674598..f91a0bc1 100644 --- a/gosubl/margo_sublime.py +++ b/gosubl/margo_sublime.py @@ -33,6 +33,41 @@ class MargoRenderSrcCommand(sublime_plugin.TextCommand): def run(self, edit, src): render_src(self.view, edit, src) +class MargoUserCmdsCommand(sublime_plugin.TextCommand): + def enabled(self): + return mg.enabled(self.view) + + def run(self, edit): + mg.send(view=self.view, actions=[actions.QueryUserCmds], cb=self._cb) + + def _cb(self, rs): + win = self.view.window() or sublime.active_window() + selected = 0 + flags = sublime.MONOSPACE_FONT + items = [] + cmds = rs.state.user_cmds + + for c in cmds: + desc = c.desc or '`%s`' % ' '.join([c.name] + c.args) + items.append([c.title, desc]) + + def on_done(i): + if i < 0 or i >= len(cmds): + return + + cmd = cmds[i] + win.run_command('gs9o_win_open', { + 'run': [cmd.name] + cmd.args, + 'save_hist': False, + 'focus_view': False, + 'show_view': True, + }) + + def on_highlight(i): + pass + + win.show_quick_panel(items or ['No User Commands'], on_done, flags, selected, on_highlight) + class MargoIssuesCommand(sublime_plugin.TextCommand): def run(self, edit, **action): if mg.enabled(self.view): diff --git a/gosubl/sh.py b/gosubl/sh.py index c9700a6c..cfdb20ad 100644 --- a/gosubl/sh.py +++ b/gosubl/sh.py @@ -48,11 +48,6 @@ def proc(self): else: input = None - try: - setsid = os.setsid - except Exception: - setsid = None - out = '' err = '' exc = None @@ -82,7 +77,7 @@ def proc(self): shell=False, env=nv, cwd=wd, - preexec_fn=setsid, + start_new_session=True, bufsize=0 ) except Exception as e: @@ -321,6 +316,8 @@ def env(m={}): m = m.copy() del m['PATH'] + add_path.append(gs.dist_path('bin')) + add_path.append(gs.user_path('bin')) add_path.append(bin_dir()) e = st_environ.copy() diff --git a/gs9o.py b/gs9o.py index 39be5bee..46802677 100644 --- a/gs9o.py +++ b/gs9o.py @@ -4,6 +4,8 @@ from .gosubl import gsshell from .gosubl import mg9 from .gosubl import sh +from .gosubl.margo import mg +from .gosubl.margo_state import actions import datetime import json import os @@ -65,18 +67,31 @@ def active_wd(win=None): _, v = gs.win_view(win=win) return gs.basedir_or_cwd(v.file_name() if v else '') +_9o_instance_default = '9o' + +def _9o_instance(wd): + name = gs.setting('9o_instance') or _9o_instance_default + if name == 'auto': + name = wd or name + + return name.replace('#', '~') + +def _rkey(wd): + _, rkey = mg.run_tokens.next() + return rkey.replace('#', '~') + +def _rcmd_wdid_rkey(*, fd): + l = fd.split('#', 1) + return (l[0], l[1]) if len(l) == 2 else (_wdid(_9o_instance_default), l[0]) + +def _rcmd_fd(*, wd, rkey): + return '%s#%s' % (_wdid(wd), rkey) + def _hkey(wd): - name = gs.setting("9o_instance") - if name: - wd = name - return '9o.hist.%s' % wd + return '9o.hist.%s' % _9o_instance(wd) def _wdid(wd): - name = gs.setting("9o_instance") - if name: - return name - return '9o://%s' % wd - + return '9o://%s' % _9o_instance(wd) class EV(sublime_plugin.EventListener): def on_query_completions(self, view, prefix, locations): @@ -232,45 +247,60 @@ def run(self, edit, wd=None): os.chdir(wd) class Gs9oOpenCommand(sublime_plugin.TextCommand): - def run(self, edit, wd=None, run=[], save_hist=False, focus_view=True): - self.view.window().run_command('gs9o_win_open', { - 'wd': wd, - 'run': run, - 'save_hist': save_hist, - 'focus_view': focus_view, - }) + def run(self, edit, **kw): + win = self.view.window() or sublime.active_window() + win.run_command('gs9o_win_open', kw) class Gs9oWinOpenCommand(sublime_plugin.WindowCommand): - def run(self, wd=None, run=[], save_hist=False, focus_view=True): + def run( + self, + wd = None, + run = [], + save_hist = False, + focus_view = True, + show_view = True, + env = {}, + push_output = [], + wdid = '', + ): win = self.window wid = win.id() if not wd: wd = active_wd(win=win) - id = _wdid(wd) + id = wdid or _wdid(wd) st = stash.setdefault(wid, {}) v = st.get(id) if v is None: v = win.get_output_panel(id) st[id] = v - win.run_command("show_panel", {"panel": ("output.%s" % id)}) + if show_view: + win.run_command("show_panel", {"panel": ("output.%s" % id)}) if focus_view: win.focus_view(v) - v.run_command('gs9o_init', {'wd': wd}) + if not push_output: + v.run_command('gs9o_init', {'wd': wd}) + + if push_output: + v.run_command('gs9o_push_output', push_output) if run: - v.run_command('gs9o_paste_exec', {'cmd': ' '.join(run), 'save_hist': save_hist}) + v.run_command('gs9o_paste_exec', { + 'cmd': ' '.join(shlex.quote(s) for s in run), + 'save_hist': save_hist, + 'env': env, + }) class Gs9oPasteExecCommand(sublime_plugin.TextCommand): - def run(self, edit, cmd, save_hist=False): + def run(self, edit, cmd, save_hist=False, env={}): view = self.view view.insert(edit, view.line(view.size()-1).end(), cmd) view.sel().clear() view.sel().add(view.line(view.size()-1).end()) - view.run_command('gs9o_exec', {'save_hist': save_hist}) + view.run_command('gs9o_exec', {'save_hist': save_hist, 'env': env}) class Gs9oOpenSelectionCommand(sublime_plugin.TextCommand): def is_enabled(self): @@ -309,7 +339,7 @@ def act_on_path(view, path): row = 0 col = 0 - m = gs.VFN_ID_PAT.match(path) + m = gs.VFN_ID_PAT.search(path) if m: path = 'gs.view://%s' % m.group(1) m2 = gs.ROWCOL_PAT.match(m.group(2)) @@ -357,7 +387,7 @@ def is_enabled(self): pos = gs.sel(self.view).begin() return self.view.score_selector(pos, 'text.9o') > 0 - def run(self, edit, save_hist=False): + def run(self, edit, save_hist=False, env={}): view = self.view pos = gs.sel(view).begin() line = view.line(pos) @@ -402,12 +432,14 @@ def run(self, edit, save_hist=False): view.run_command('gs9o_init') return + rkey = _rkey(wd) + # view.add_regions(rkey, [sublime.Region(line.begin(), view.size())], '') + view.add_regions(rkey, [line], '') view.replace(edit, line, (u'[ `%s` %s ]' % (cmd, HOURGLASS))) - rkey = '9o.exec.%s' % uuid.uuid4() - view.add_regions(rkey, [sublime.Region(line.begin(), view.size())], '') view.run_command('gs9o_init') nv = sh.env() + nv.update(env) anv = nv.copy() seen = {} am = aliases() @@ -432,56 +464,65 @@ def run(self, edit, save_hist=False): cmd = string.Template(alias).safe_substitute(anv) if nm != 'sh': + args = [] + if ag: + args = [_exparg(s, nv) for s in shlex.split(gs.astr(ag))] + f = builtins().get(nm) if f: - args = [] - if ag: - args = [_exparg(s, nv) for s in shlex.split(gs.astr(ag))] - f(view, edit, args, wd, rkey) return + else: + _rcmd(view, edit, nm, args, wd, rkey) + return if nm == 'sh': args = sh.cmd(ag) + cmd_sh(view, edit, args, wd, rkey) else: args = sh.cmd(cmd) - - cmd_sh(view, edit, args, wd, rkey) else: view.insert(edit, gs.sel(view).begin(), '\n') class Gs9oPushOutput(sublime_plugin.TextCommand): - def run(self, edit, rkey, output, hourglass_repl=''): + def run(self, edit, rkey, output, hourglass_repl='', done=True, append_only=False): view = self.view - output = '\t%s' % gs.ustr(output).strip().replace('\r', '').replace('\n', '\n\t') + if not append_only: + output = '\t%s' % gs.ustr(output).strip().replace('\r', '').replace('\n', '\n\t') + regions = view.get_regions(rkey) if regions: - line = view.line(regions[0].begin()) - lsrc = view.substr(line).replace(HOURGLASS, (hourglass_repl or '| done')) - view.replace(edit, line, lsrc) - r = line + prompt = view.line(regions[0].begin()) + if done: + lsrc = view.substr(prompt).replace(HOURGLASS, (hourglass_repl or '| done')) + view.replace(edit, prompt, lsrc) + + regions = view.get_regions(rkey) + r = view.line(regions[-1].end()) if output.strip(): - line = view.line(regions[0].begin()) - view.insert(edit, line.end(), '\n%s' % output) - r = view.get_regions(rkey)[0] + n = view.insert(edit, r.end(), '\n%s' % output) + view.erase_regions(rkey) + view.add_regions(rkey, [sublime.Region(prompt.begin(), r.end() + n)]) else: n = view.size() view.insert(edit, n, '\n%s' % output) r = sublime.Region(n, view.size()) - if gs.setting('9o_show_end') is True: - view.show(r.end()) - else: - view.show(r.begin()) + if not append_only and done: + if gs.setting('9o_show_end') is True: + view.show(r.end()) + else: + view.show(r.begin()) class Gs9oRunManyCommand(sublime_plugin.TextCommand): - def run(self, edit, wd=None, commands=[], save_hist=False, focus_view=False): + def run(self, edit, wd=None, commands=[], save_hist=False, focus_view=False, show_view=True): for run in commands: self.view.run_command("gs9o_open", { 'run': run, 'wd': wd, 'save_hist': save_hist, 'focus_view': focus_view, + 'show_view': show_view, }) def aliases(): @@ -499,15 +540,14 @@ def builtins(): return m -def push_output(view, rkey, output, hourglass_repl=''): - def f(): - view.run_command('gs9o_push_output', { - 'rkey': rkey, - 'output': output, - 'hourglass_repl': hourglass_repl, - }) - - sublime.set_timeout(f, 0) +def push_output(view, rkey, output, hourglass_repl='', done=True, append_only=False): + view.run_command('gs9o_push_output', { + 'rkey': rkey, + 'output': output, + 'hourglass_repl': hourglass_repl, + 'done': done, + 'append_only': append_only, + }) def _save_all(win, wd): if gs.setting('autosave') is True and win is not None: @@ -550,14 +590,36 @@ def f(): return cid, cb -def cmd_margo_reinstall(view, edit, args, wd, rkey): - def cb(): - gs.del_attr(mg9._inst_name()) - out = mg9.install('', True, True) - gs.notify(DOMAIN, 'MarGo re-installed done') - push_output(view, rkey, out) - - gsq.launch(DOMAIN, cb) +def _rcmd_output_handler(rs, act): + wdid, rkey = _rcmd_wdid_rkey(fd=act.fd) + sublime.active_window().run_command('gs9o_win_open', { + 'wdid': wdid, + 'focus_view': False, + 'show_view': False, + 'push_output': { + 'rkey': rkey, + 'output': act.output, + 'done': act.close, + 'append_only': False, + }, + }) + +mg.output_handler = _rcmd_output_handler + +def _rcmd(view, edit, nm, args, wd, rkey): + def cb(rs): + if rs.error: + push_output(view, rkey, rs.error) + + act = actions.RunCmd.copy() + act['Data'] = { + 'Fd': _rcmd_fd(wd=wd, rkey=rkey), + 'Name': nm, + 'Args': args, + } + # `view` is the 9o view, but the command wants the `active/editor view` + run_view = None + mg.send(view=run_view, cb=cb, actions=[act]) def cmd_echo(view, edit, args, wd, rkey): push_output(view, rkey, ' '.join(args)) @@ -596,7 +658,9 @@ def cmd_cd(view, edit, args, wd, rkey): wd = args[0] wd = string.Template(wd).safe_substitute(sh.env()) wd = os.path.expanduser(wd) + print('>'+wd) wd = os.path.abspath(wd) + print('<'+wd) else: fn = view.window().active_view().file_name() if fn: @@ -614,24 +678,15 @@ def cmd_reset(view, edit, args, wd, rkey): push_output(view, rkey, '') view.erase(edit, sublime.Region(0, view.size())) view.run_command('gs9o_init') + if args: + view.run_command('gs9o_paste_exec', {'cmd': ' '.join(args), 'save_hist': False}) def cmd_clear(view, edit, args, wd, rkey): cmd_reset(view, edit, args, wd, rkey) def cmd_go(view, edit, args, wd, rkey): _save_all(view.window(), wd) - - cid, cb = _9_begin_call('go', view, edit, args, wd, rkey, '9go-%s' % wd) - a = { - 'cid': cid, - 'env': sh.env(), - 'cwd': wd, - 'cmd': { - 'name': 'go', - 'args': args, - } - } - sublime.set_timeout(lambda: mg9.acall('sh', a, cb), 0) + _rcmd(view, edit, 'go', args, wd, rkey) def cmd_cancel_replay(view, edit, args, wd, rkey): cid = '' @@ -682,7 +737,8 @@ def cmd_run(view, edit, args, wd, rkey): cmd_9(view, edit, gs.lst('run', args), wd, rkey) def cmd_replay(view, edit, args, wd, rkey): - cmd_9(view, edit, gs.lst('replay', args), wd, rkey) + _save_all(view.window(), wd) + _rcmd(view, edit, 'replay', args, wd, rkey) def cmd_build(view, edit, args, wd, rkey): cmd_9(view, edit, gs.lst('build', args), wd, rkey) From 268fa6dcb6e596b322e1c0a1e9fbc8cb65f1d57b Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Wed, 11 Apr 2018 11:10:57 +0100 Subject: [PATCH 013/186] don't do autocompletion in views without a lang, nothing will respond to it --- gosubl/margo.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gosubl/margo.py b/gosubl/margo.py index 96b847b0..e1857dab 100644 --- a/gosubl/margo.py +++ b/gosubl/margo.py @@ -155,6 +155,10 @@ def send(self, *, actions=[], cb=None, view=None): return self.agent.send(actions=actions, cb=cb, view=view) def on_query_completions(self, view, prefix, locations): + _, lang = _view_scope_lang(view, 0) + if not lang: + return None + rs = self.send(view=view, actions=[actions.QueryCompletions]).wait(0.300) if not rs: self.out.println('aborting QueryCompletions. it did not respond in time') From b9ac9d963dedb6b9ef16e8ff8126a2f8c0784ab3 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sat, 14 Apr 2018 11:03:17 +0100 Subject: [PATCH 014/186] improve VFN matching * change the primary VFN from `view#$ID` to `view@$ID` mainly to avoid confusion with 9o's `#` * relax VFN-related regexps to not require a match at the start of the line * clean up the VFN name a little --- gosubl/gs.py | 16 ++++++++++------ gosubl/margo_state.py | 9 ++++++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/gosubl/gs.py b/gosubl/gs.py index f2cc165d..6a804057 100644 --- a/gosubl/gs.py +++ b/gosubl/gs.py @@ -154,8 +154,8 @@ 'constant.other.rune.go', ]) -VFN_ID_PAT = re.compile(r'^(?:gs\.)?view(?:#|://)(\d+)(.*?)$', re.IGNORECASE) -ROWCOL_PAT = re.compile(r'^[:]*(\d+)(?:[:](\d+))?[:]*$') +VFN_ID_PAT = re.compile(r'(?:gs\.)?view(?:#|@|://)(\d+)(.*?)$', re.IGNORECASE) +ROWCOL_PAT = re.compile(r'[:]+(\d+)(?:[:](\d+))?[:]*$') USER_DIR = os.path.expanduser('~') USER_DIR_PAT = re.compile(r'^%s/' % (re.escape(USER_DIR.replace('\\', '/').rstrip('/')))) @@ -504,11 +504,12 @@ def active_view(win=None, view=None): return win.active_view() def win_view(vfn=None, win=None): - if not win: - win = sublime.active_window() + wins = [win] + if win is None: + wins = sublime.windows() view = None - if win: + for win in wins: m = VFN_ID_PAT.match(vfn or '') if m: try: @@ -519,10 +520,13 @@ def win_view(vfn=None, win=None): break except Exception: gs.error_traceback(NAME) - elif not vfn or vfn == "": + + if view is None: + if not vfn or vfn == "": view = win.active_view() else: view = win.open_file(vfn) + return (win, view) def do_focus(fn, row, col, win, focus_pat, cb): diff --git a/gosubl/margo_state.py b/gosubl/margo_state.py index 1dd312fa..d3213c25 100644 --- a/gosubl/margo_state.py +++ b/gosubl/margo_state.py @@ -1,6 +1,7 @@ from . import _dbg from . import gs, sh from .margo_common import NS +from os.path import basename, splitext import os import re import sublime @@ -203,6 +204,8 @@ def _view_props(view): return props +_sanitize_view_name_pat = re.compile(r'[^-~,.@\w]') + def view_name(view, ext='', lang=''): if view is None: return '' @@ -210,7 +213,11 @@ def view_name(view, ext='', lang=''): if not ext: ext = _view_ext(view, lang=lang) - return 'view#' + _view_id(view) + ext + nm = basename(view.file_name() or view.name() or '_') + nm, _ = splitext(nm) + nm = 'view@%s,%s%s' % (_view_id(view), nm, ext) + nm = _sanitize_view_name_pat.sub('', nm) + return nm def view_path(view): if view is None: From 84986207fb53eaa42b821fa7ca05422946538064 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sat, 14 Apr 2018 11:07:18 +0100 Subject: [PATCH 015/186] close the agent stderr after the process exits to allow logging to continue to work partially during shutdown --- gosubl/margo_agent.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/gosubl/margo_agent.py b/gosubl/margo_agent.py index 276bcd1d..cc92b378 100644 --- a/gosubl/margo_agent.py +++ b/gosubl/margo_agent.py @@ -117,6 +117,7 @@ def _start_proc(self): self.out.println('Cannot start margo: %s' % pr.exc) return + stderr = pr.p.stderr self.proc = pr.p gsq.launch(self.domain, self._handle_send) gsq.launch(self.domain, self._handle_acts) @@ -125,6 +126,7 @@ def _start_proc(self): self.started.set() self.starting.clear() self.proc.wait() + self._close_file(stderr) def _stop_proc(self): self.out.println('stopping') @@ -132,12 +134,19 @@ def _stop_proc(self): if not p: return - for f in (p.stdin, p.stdout, p.stderr): - try: - f.close() - except Exception as exc: - self.out.println(exc) - gs.error_traceback(self.domain) + # stderr is closed after .wait() returns + for f in (p.stdin, p.stdout): + self._close_file(f) + + def _close_file(self, f): + if f is None: + return + + try: + f.close() + except Exception as exc: + self.out.println(exc) + gs.error_traceback(self.domain) def _handle_send_ipc(self, rq): with self.lock: From 6cedf24df8fcac0235ec5d6cbf4dae8f0579e207 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sat, 14 Apr 2018 11:35:37 +0100 Subject: [PATCH 016/186] improve issue palette rendering * add support for multiline messages * prevent ST from cropping the message sometimes when it's longer than the title line * show the tag and label below the message --- gosubl/margo_sublime.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/gosubl/margo_sublime.py b/gosubl/margo_sublime.py index f91a0bc1..5607f178 100644 --- a/gosubl/margo_sublime.py +++ b/gosubl/margo_sublime.py @@ -118,8 +118,21 @@ def sort_key(isu): title = '%s:%d' % (isu.relpath(dir) or isu.name, isu.row + 1) selected.append((999999999, -1)) - message = ' %s%s' % (isu.message, ' [' + isu.label + ']' if isu.label else '') - items.append([title, message]) + rows = [title] + rows.extend(s.strip() for s in isu.message.split('\n')) + rows.append(' '.join( + '[%s]' % s for s in filter(bool, (isu.tag, isu.label)) + )) + + # hack: ST sometimes decide to truncate the message because it's longer + # than the top row... and we don't want the message up there + rows[0] = rows[0].ljust(max(len(s) for s in rows)) + items.append(rows) + + # hack: if the items don't have the same length, ST throws an exception + n = max(len(l) for l in items) + for l in items: + l += [''] * (n - len(l)) return (items, index, min(selected)[1]) From 36420032c62ec1a68fe62dc686eb3ec7415957f7 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sat, 14 Apr 2018 13:32:20 +0100 Subject: [PATCH 017/186] make view path/name matching consistent between GoSublime and margo --- gosubl/margo_render.py | 7 +++---- gosubl/margo_state.py | 41 ++++++++++++++++++++++++++++++++-------- gosubl/margo_sublime.py | 22 ++++++++++----------- src/margo.sh/mg/issue.go | 33 +++++++++++++++++++++++--------- 4 files changed, 71 insertions(+), 32 deletions(-) diff --git a/gosubl/margo_render.py b/gosubl/margo_render.py index 2c796d37..ab966d4a 100644 --- a/gosubl/margo_render.py +++ b/gosubl/margo_render.py @@ -1,7 +1,7 @@ from . import _dbg from . import gs from . import gspatch -from .margo_state import view_name, view_path +from .margo_state import ViewPathName import sublime STATUS_KEY = '#mg.Status' @@ -64,10 +64,9 @@ def __init__(self, *, key, scope, icon, flags): def _render_issues(view, issues): regions = {cfg.key: (cfg, []) for cfg in issue_cfgs.values()} - path = view_path(view) - name = view_name(view) + vp = ViewPathName(view) for isu in issues: - if path == isu.path or name == isu.name: + if isu.match(vp): cfg = issue_cfgs.get(isu.tag) or issue_cfg_default regions[cfg.key][1].append(_render_issue(view, isu)) diff --git a/gosubl/margo_state.py b/gosubl/margo_state.py index d3213c25..eba4a751 100644 --- a/gosubl/margo_state.py +++ b/gosubl/margo_state.py @@ -98,10 +98,36 @@ def __init__(self, v): def __repr__(self): return repr(self.__dict__) -class Issue(object): +class PathName(object): + def __init__(self, *, path, name): + self.path = path or '' + self.name = name or '' + + def match(self, p): + if self.path and self.path == p.path: + return True + + if self.name and self.name == p.name: + return True + + return False + + def __repr__(self): + return repr(vars(self)) + +class ViewPathName(PathName): + def __init__(self, view): + super().__init__( + path = view_path(view), + name = view_name(view), + ) + +class Issue(PathName): def __init__(self, v): - self.path = v.get('Path') or '' - self.name = v.get('Name') or '' + super().__init__( + path = v.get('Path') or '', + name = v.get('Name') or '', + ) self.hash = v.get('Hash') or '' self.row = v.get('Row') or 0 self.col = v.get('Col') or 0 @@ -208,13 +234,12 @@ def _view_props(view): def view_name(view, ext='', lang=''): if view is None: - return '' - - if not ext: - ext = _view_ext(view, lang=lang) + return '_._' nm = basename(view.file_name() or view.name() or '_') - nm, _ = splitext(nm) + nm, nm_ext = splitext(nm) + if not ext: + ext = _view_ext(view, lang=lang) or nm_ext or '._' nm = 'view@%s,%s%s' % (_view_id(view), nm, ext) nm = _sanitize_view_name_pat.sub('', nm) return nm diff --git a/gosubl/margo_sublime.py b/gosubl/margo_sublime.py index 5607f178..78e1a2c0 100644 --- a/gosubl/margo_sublime.py +++ b/gosubl/margo_sublime.py @@ -2,7 +2,7 @@ from . import gs from .margo import mg from .margo_render import render_src -from .margo_state import actions, view_path, view_name +from .margo_state import actions, ViewPathName import os import sublime import sublime_plugin @@ -84,14 +84,10 @@ def _cb(self, rs): show_issues(self.view, rs.state.issues) def issues_to_items(view, issues): - path = view_path(view) - dir = os.path.dirname(path) - name = view_name(view) + vp = ViewPathName(view) + dir = os.path.dirname(vp.path) index = [] - def in_view(isu): - return isu.path == path or isu.name == name or (not isu.path and not isu.name) - for isu in issues: if isu.message: index.append(isu) @@ -100,7 +96,7 @@ def in_view(isu): return ([], [], -1) def sort_key(isu): - if in_view(isu): + if vp.match(isu): return (-1, '', isu.row) return (1, isu.relpath(dir), isu.row) @@ -111,7 +107,7 @@ def sort_key(isu): items = [] selected = [] for idx, isu in enumerate(index): - if in_view(isu): + if vp.match(isu): title = 'Line %d' % (isu.row + 1) selected.append((abs(isu.row - row), idx)) else: @@ -142,8 +138,12 @@ def show_issues(view, issues): items, index, selected = issues_to_items(view, issues) def on_done(i): - if i < 0 or i >= len(items): - fn = view_path(view) or view_name(view) + if not index or i >= len(index): + return + + if i < 0: + vp = ViewPathName(view) + fn = vp.path or vp.name gs.focus(fn, row=orig_row, col=orig_col, win=view.window()) return diff --git a/src/margo.sh/mg/issue.go b/src/margo.sh/mg/issue.go index 64063e30..7d6eb871 100644 --- a/src/margo.sh/mg/issue.go +++ b/src/margo.sh/mg/issue.go @@ -48,10 +48,13 @@ func (isu Issue) SameFile(p Issue) bool { } func (isu Issue) InView(v *View) bool { - if isu.Path != "" { - return v.Path == isu.Path + if isu.Path != "" && isu.Path == v.Path { + return true + } + if isu.Name != "" && isu.Name == v.Name { + return true } - return isu.Name == v.Name + return false } func (isu Issue) Valid() bool { @@ -145,13 +148,25 @@ func (iks *issueKeySupport) Reduce(mx *Ctx) *State { } issues := IssueSet{} - clean := filepath.Clean - name := clean(mx.View.Name) - path := clean(mx.View.Path) - dir := clean(mx.View.Dir()) + norm := filepath.Clean + name := norm(mx.View.Name) + path := norm(mx.View.Path) + dir := norm(mx.View.Dir()) + match := func(k IssueKey) bool { + if path != "" && path == k.Path { + return true + } + if name != "" && name == k.Name { + return true + } + if dir != "" && dir == k.Dir { + return true + } + return false + } for k, v := range iks.issues { - if k.Name == name || clean(k.Path) == path || clean(k.Dir) == dir { - issues = issues.Add(v...) + if match(k) { + issues = append(issues, v...) } } From 147037396f4b869f34f6a97a7260b787031e77b7 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sat, 14 Apr 2018 14:18:22 +0100 Subject: [PATCH 018/186] sync margo --- src/margo.sh/Gopkg.lock | 2 +- src/margo.sh/golang/gocmd.go | 223 +++++++++++++++--- src/margo.sh/mg/action.go | 6 + src/margo.sh/mg/agent.go | 67 ++++-- src/margo.sh/mg/builtins.go | 53 +---- src/margo.sh/mg/cmd.go | 148 ++++++++---- src/margo.sh/mg/cmd_nix.go | 13 +- src/margo.sh/mg/cmd_win.go | 18 ++ src/margo.sh/mg/db.go | 110 ++++++++- src/margo.sh/mg/state.go | 37 +-- src/margo.sh/mg/store.go | 70 +++--- src/margo.sh/mg/tasks.go | 76 +++++- src/margo.sh/mg/view.go | 56 ++++- .../x/crypto/blake2b/blake2bAVX2_amd64.go | 24 +- .../x/crypto/blake2b/blake2bAVX2_amd64.s | 12 - 15 files changed, 674 insertions(+), 241 deletions(-) create mode 100644 src/margo.sh/mg/cmd_win.go diff --git a/src/margo.sh/Gopkg.lock b/src/margo.sh/Gopkg.lock index 6fde3f9a..eac65e84 100644 --- a/src/margo.sh/Gopkg.lock +++ b/src/margo.sh/Gopkg.lock @@ -17,7 +17,7 @@ branch = "master" name = "golang.org/x/crypto" packages = ["blake2b"] - revision = "b2aa35443fbc700ab74c586ae79b81c171851023" + revision = "d6449816ce06963d9d136eee5a56fca5b0616e7e" [solve-meta] analyzer-name = "dep" diff --git a/src/margo.sh/golang/gocmd.go b/src/margo.sh/golang/gocmd.go index 30cbf9fa..84fbbd4b 100644 --- a/src/margo.sh/golang/gocmd.go +++ b/src/margo.sh/golang/gocmd.go @@ -2,7 +2,11 @@ package golang import ( "fmt" + "go/build" + "io/ioutil" "margo.sh/mg" + "os" + "path/filepath" "strings" ) @@ -10,68 +14,213 @@ type GoCmd struct{} func (gc *GoCmd) Reduce(mx *mg.Ctx) *mg.State { switch act := mx.Action.(type) { + case mg.QueryUserCmds: + return gc.userCmds(mx) case mg.RunCmd: return gc.runCmd(mx, act) } return mx.State } +func (gc *GoCmd) userCmds(mx *mg.Ctx) *mg.State { + return mx.AddUserCmds( + mg.UserCmd{ + Name: "go.play", + Title: "Go Play", + }, + mg.UserCmd{ + Name: "go.replay", + Title: "Go RePlay (single instance)", + }, + ) +} + func (gc *GoCmd) runCmd(mx *mg.Ctx, rc mg.RunCmd) *mg.State { return mx.State.AddBuiltinCmds( - mg.BultinCmd{Run: gc.gotoolCmd, Name: "go", Desc: "" + - "Wrapper around the go command." + - " It adds `go .play` and `go .replay` as well as add linter support." + - "", + mg.BultinCmd{ + Run: gc.goBuiltin, + Name: "go", + Desc: "Wrapper around the go command, adding linter support", + }, + mg.BultinCmd{ + Run: gc.playBuiltin, + Name: "go.play", + Desc: "Automatically build and run go commands or run go test", + }, + mg.BultinCmd{ + Run: gc.replayBuiltin, + Name: "go.replay", + Desc: "Wrapper around .play limited to a single instance", }, ) } -func (gc *GoCmd) subCmd(args []string) string { - for _, s := range args { - if s != "" && !strings.HasPrefix(s, "-") { - return s - } - } - return "" +func (gc *GoCmd) goBuiltin(bx *mg.BultinCmdCtx) *mg.State { + go gc.goTool(bx) + return bx.State } -func (gc *GoCmd) gotoolCmd(bx *mg.BultinCmdCtx) *mg.State { - go gc.gotool(bx) +func (gc *GoCmd) playBuiltin(bx *mg.BultinCmdCtx) *mg.State { + go gc.playTool(bx, "") return bx.State } -func (gc *GoCmd) gotool(bx *mg.BultinCmdCtx) { - defer bx.Close() +func (gc *GoCmd) replayBuiltin(bx *mg.BultinCmdCtx) *mg.State { + go gc.playTool(bx, "go.replay") + return bx.State +} - subCmd := gc.subCmd(bx.Args) - dir := bx.View.Dir() - // TODO: detect pkgDir passed os args - pkgDir := dir - type Key struct{ subCmd, pkgDir string } - key := Key{subCmd, pkgDir} +func (gc *GoCmd) goTool(bx *mg.BultinCmdCtx) { + gx := newGoCmdCtx(bx, "go.builtin", "", "", "") + defer gx.Output.Close() + gx.run(gx.View) +} - iw := &mg.IssueWriter{ - Patterns: CommonPatterns, - Base: mg.Issue{Label: strings.TrimSpace("go " + subCmd)}, - Dir: dir, +func (gc *GoCmd) playTool(bx *mg.BultinCmdCtx, cancelID string) { + origView := bx.View + bx, tDir, tFn, err := gc.playTempDir(bx) + gx := newGoCmdCtx(bx, "go.play", cancelID, tDir, tFn) + defer gx.Output.Close() + + if err != nil { + fmt.Fprintf(gx.Output, "Error: %s\n", err) + } + if tDir == "" { + return + } + defer os.RemoveAll(tDir) + + bld := BuildContext(gx.Ctx) + pkg, err := bld.ImportDir(gx.pkgDir, 0) + switch { + case err != nil: + fmt.Fprintln(gx.Output, "Error: cannot import package:", err) + case !pkg.IsCommand() || strings.HasSuffix(bx.View.Filename(), "_test.go"): + gc.playToolTest(gx, bld, origView) + default: + gc.playToolRun(gx, bld, origView) + } +} + +func (gc *GoCmd) playTempDir(bx *mg.BultinCmdCtx) (newBx *mg.BultinCmdCtx, tDir string, tFn string, err error) { + tDir, err = mg.MkTempDir("go.play") + if err != nil { + return bx, "", "", fmt.Errorf("cannot MkTempDir: %s", err) + } + + if !bx.LangIs("go") { + return bx, tDir, "", nil + } + + v := bx.View + if v.Path != "" { + return bx, tDir, tFn, nil + } + + tFn = filepath.Join(tDir, v.Name) + src, err := v.ReadAll() + if err == nil { + err = ioutil.WriteFile(tFn, src, 0600) + } + if err != nil { + return bx, tDir, "", fmt.Errorf("cannot create temp file: %s", err) } bx = bx.Copy(func(bx *mg.BultinCmdCtx) { - bx.Output = &mg.CmdOutputWriter{ - Fd: bx.Output.Fd, - Writer: iw, - } + bx.Ctx = bx.Ctx.Copy(func(mx *mg.Ctx) { + mx.State = mx.State.Copy(func(st *mg.State) { + st.View = st.View.Copy(func(v *mg.View) { + v.Path = tFn + }) + }) + }) }) - err := bx.RunProc() - if err != nil { - fmt.Fprintln(bx.Output, err) + return bx, tDir, tFn, nil +} + +func (gc *GoCmd) playToolTest(gx *goCmdCtx, bld *build.Context, origView *mg.View) { + gx.Args = append([]string{"test"}, gx.Args...) + gx.run(origView) +} + +func (gc *GoCmd) playToolRun(gx *goCmdCtx, bld *build.Context, origView *mg.View) { + args := gx.Args + exe := filepath.Join(gx.tDir, "margo.play~~"+filepath.Base(gx.tFn)+".exe") + gx.Name = "go" + gx.Args = []string{"build", "-o", exe} + gx.View = gx.View.Copy() + gx.View.Wd = gx.View.Dir() + if err := gx.run(origView); err != nil { return } - iw.Flush() - bx.Store.Dispatch(mg.StoreIssues{ - Key: mg.IssueKey{Key: key, Dir: pkgDir}, - Issues: iw.Issues(), - }) + gx.View = origView + gx.Name = exe + gx.Args = args + gx.RunProc() +} + +type goCmdCtx struct { + *mg.BultinCmdCtx + pkgDir string + key interface{} + iw *mg.IssueWriter + tDir string + tFn string +} + +func newGoCmdCtx(bx *mg.BultinCmdCtx, label, cancelID string, tDir, tFn string) *goCmdCtx { + gx := &goCmdCtx{ + BultinCmdCtx: bx.Copy(), + pkgDir: bx.View.Dir(), + tDir: tDir, + tFn: tFn, + } + + type Key struct{ label string } + gx.key = Key{label} + + gx.iw = &mg.IssueWriter{ + Base: mg.Issue{Label: label}, + Patterns: CommonPatterns, + Dir: gx.View.Wd, + } + if tDir != "" { + gx.iw.Dir = tDir + } + + gx.Name = "go" + gx.CancelID = cancelID + gx.Output = gx.Output.Copy() + gx.Output.Writer = gx.iw + + return gx +} + +func (gx *goCmdCtx) run(origView *mg.View) error { + p, err := gx.StartProc() + if err == nil { + err = p.Wait() + } + gx.iw.Flush() + + issues := gx.iw.Issues() + for i, isu := range issues { + if isu.Path == gx.tFn { + isu.Name = origView.Name + isu.Path = origView.Path + } + issues[i] = isu + } + + ik := mg.IssueKey{Key: gx.key} + if origView.Path == "" { + ik.Name = origView.Name + } else { + ik.Dir = origView.Dir() + } + + gx.Store.Dispatch(mg.StoreIssues{Key: ik, Issues: issues}) + return err } diff --git a/src/margo.sh/mg/action.go b/src/margo.sh/mg/action.go index 64b6ee72..1d4dffd6 100644 --- a/src/margo.sh/mg/action.go +++ b/src/margo.sh/mg/action.go @@ -80,6 +80,12 @@ type QueryTooltips struct{ ActionType } type Restart struct{ ActionType } +// Shutdown is the action dispatched when margo is shutting down. +// Reducers may use it as a signal to do any cleanups with the following caveats: +// * it might not be dispatched at all +// * it might be dispatched multiple times +// * IPC might not be available so state changes might have no effect +// * logging should, but might not, be available type Shutdown struct{ ActionType } type ViewActivated struct{ ActionType } diff --git a/src/margo.sh/mg/agent.go b/src/margo.sh/mg/agent.go index 75d50938..17a67b05 100644 --- a/src/margo.sh/mg/agent.go +++ b/src/margo.sh/mg/agent.go @@ -74,8 +74,8 @@ type agentReq struct { Props clientProps } -func newAgentReq() *agentReq { - return &agentReq{Props: makeClientProps()} +func newAgentReq(kvs KVStore) *agentReq { + return &agentReq{Props: makeClientProps(kvs)} } func (rq *agentReq) finalize(ag *Agent) { @@ -120,8 +120,8 @@ func (rs agentRes) finalize() interface{} { } type Agent struct { - Name string - + Name string + Done <-chan struct{} Log *Logger Store *Store @@ -135,10 +135,18 @@ type Agent struct { enc *codec.Encoder encWr *bufio.Writer dec *codec.Decoder + wg sync.WaitGroup + + sd struct { + mu sync.Mutex + done chan<- struct{} + closed bool + } + closed bool } func (ag *Agent) Run() error { - defer ag.shutdownIPC() + defer ag.shutdown() return ag.communicate() } @@ -148,7 +156,7 @@ func (ag *Agent) communicate() error { ag.Store.ready() for { - rq := newAgentReq() + rq := newAgentReq(ag.Store) if err := ag.dec.Decode(rq); err != nil { if err == io.EOF { return nil @@ -156,15 +164,23 @@ func (ag *Agent) communicate() error { return fmt.Errorf("ipc.decode: %s", err) } rq.finalize(ag) - // TODO: put this on a channel in the future. - // at the moment we lock the store and block new requests to maintain request/response order - // but decoding time could become a problem if we start sending large requests from the client - // we currently only have 1 client (GoSublime) that we also control so it's ok for now... - ag.Store.syncRq(ag, rq) + ag.handleReq(rq) } return nil } +func (ag *Agent) handleReq(rq *agentReq) { + ag.wg.Add(1) + defer ag.wg.Done() + + // TODO: put this on a channel in the future. + // at the moment we lock the store and block new requests to maintain request/response order + // but decoding time could become a problem if we start sending large requests from the client + // we currently only have 1 client (GoSublime) that we also control so it's ok for now... + + ag.Store.syncRq(ag, rq) +} + func (ag *Agent) createAction(ra agentReqAction, h codec.Handle) (Action, error) { if f := actionCreators[ra.Name]; f != nil { return f(h, ra) @@ -176,7 +192,7 @@ func (ag *Agent) listener(st *State) { err := ag.send(agentRes{State: st}) if err != nil { ag.Log.Println("agent.send failed. shutting down ipc:", err) - go ag.shutdownIPC() + go ag.shutdown() } } @@ -188,19 +204,42 @@ func (ag *Agent) send(res agentRes) error { return ag.enc.Encode(res.finalize()) } -func (ag *Agent) shutdownIPC() { - defer ag.stdin.Close() +func (ag *Agent) shutdown() { + sd := &ag.sd + sd.mu.Lock() + defer sd.mu.Unlock() + + if sd.closed { + return + } + sd.closed = true + + // shutdown sequence: + // * stop incoming requests + // * wait for all reqs to complete + // * tell reducers we're shutting down + // * stop outgoing responses + // * tell the world we're done + + // defers because we want *some* guarantee that all these steps will be taken + defer close(sd.done) defer ag.stdout.Close() + defer ag.Store.dispatch(Shutdown{}) + defer ag.wg.Wait() + defer ag.stdin.Close() } func NewAgent(cfg AgentConfig) (*Agent, error) { + done := make(chan struct{}) ag := &Agent{ Name: cfg.AgentName, + Done: done, stdin: cfg.Stdin, stdout: cfg.Stdout, stderr: cfg.Stderr, handle: codecHandles[cfg.Codec], } + ag.sd.done = done if ag.stdin == nil { ag.stdin = os.Stdin } diff --git a/src/margo.sh/mg/builtins.go b/src/margo.sh/mg/builtins.go index 2e3bdcd1..9a44a4c0 100644 --- a/src/margo.sh/mg/builtins.go +++ b/src/margo.sh/mg/builtins.go @@ -5,7 +5,6 @@ import ( "fmt" "margo.sh/mgutil" "os" - "os/exec" "sort" ) @@ -40,7 +39,7 @@ func (bc BuiltinCmds) ExecCmd(bx *BultinCmdCtx) *State { } func (bc BuiltinCmds) execCmd(bx *BultinCmdCtx) { - defer bx.Close() + defer bx.Output.Close() if bx.Name == ".exec" { if len(bx.Args) == 0 { @@ -52,11 +51,7 @@ func (bc BuiltinCmds) execCmd(bx *BultinCmdCtx) { }) } - err := bx.RunProc() - if err == nil { - return - } - fmt.Fprintf(bx.Output, "cannot exec `%s`: %s", mgutil.QuoteCmd(bx.Name, bx.Args...), err) + bx.RunProc() } func (bc BuiltinCmds) TypeCmd(bx *BultinCmdCtx) *State { @@ -75,7 +70,7 @@ func (bc BuiltinCmds) TypeCmd(bx *BultinCmdCtx) *State { fmt.Fprintf(buf, "%s: builtin: %s, desc: %s\n", name, c.Name, c.Desc) } - bx.Close(buf.Bytes()) + bx.Output.Close(buf.Bytes()) return bx.State } @@ -93,7 +88,7 @@ func (bc BuiltinCmds) EnvCmd(bx *BultinCmdCtx) *State { v := bx.Env.Get(k, os.Getenv(k)) fmt.Fprintf(buf, "%s=%s\n", k, v) } - bx.Close(buf.Bytes()) + bx.Output.Close(buf.Bytes()) return bx.State } @@ -122,7 +117,7 @@ func NewBultinCmdCtx(mx *Ctx, rc RunCmd) *BultinCmdCtx { return &BultinCmdCtx{ Ctx: mx, RunCmd: rc, - Output: &CmdOutputWriter{Fd: rc.Fd}, + Output: &CmdOutputWriter{Fd: rc.Fd, Dispatch: mx.Store.Dispatch}, } } @@ -138,44 +133,18 @@ func (bx *BultinCmdCtx) Copy(updaters ...func(*BultinCmdCtx)) *BultinCmdCtx { return x.update(updaters...) } -func (bx *BultinCmdCtx) Close(output ...[]byte) { - for _, s := range output { - bx.Output.Write(s) - } - bx.Output.Close() - bx.Store.Dispatch(bx.Output.Output()) -} - -func (bx *BultinCmdCtx) RunProc() error { +func (bx *BultinCmdCtx) RunProc() { p, err := bx.StartProc() + if err == nil { + err = p.Wait() + } if err != nil { - return err + fmt.Fprintf(bx.Output, "cannot run `%s`: %s\n", mgutil.QuoteCmd(bx.Name, bx.Args...), err) } - return p.Wait() } func (bx *BultinCmdCtx) StartProc() (*Proc, error) { - cmd := exec.Command(bx.Name, bx.Args...) - - if bx.Input { - r, err := bx.View.Open() - if err != nil { - return nil, err - } - cmd.Stdin = r - } - - cmd.Dir = bx.View.Dir() - cmd.Env = bx.Env.Environ() - cmd.Stdout = bx.Output - cmd.Stderr = bx.Output - cmd.SysProcAttr = defaultSysProcAttr - - p := &Proc{ - done: make(chan struct{}), - bx: bx, - cmd: cmd, - } + p := newProc(bx) return p, p.start() } diff --git a/src/margo.sh/mg/cmd.go b/src/margo.sh/mg/cmd.go index e6c28889..935e5999 100644 --- a/src/margo.sh/mg/cmd.go +++ b/src/margo.sh/mg/cmd.go @@ -8,40 +8,62 @@ import ( "os" "os/exec" "sync" - "syscall" "time" ) -var ( - defaultSysProcAttr *syscall.SysProcAttr -) - type CmdOutputWriter struct { io.Writer io.Closer - Fd string + Fd string + Dispatch Dispatcher mu sync.Mutex - buf bytes.Buffer + buf []byte closed bool } +func (w *CmdOutputWriter) Copy(updaters ...func(*CmdOutputWriter)) *CmdOutputWriter { + p := *w + p.buf = append([]byte{}, w.buf...) + p.Writer = nil + p.Closer = nil + w = &p + for _, f := range updaters { + f(w) + } + return w +} + func (w *CmdOutputWriter) Write(p []byte) (int, error) { + return w.write(false, p) +} + +func (w *CmdOutputWriter) write(writeIfClosed bool, p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() - if w.closed { + if w.closed && !writeIfClosed { return 0, os.ErrClosed } - n, err := w.buf.Write(p) - if w.Writer != nil { - return w.Writer.Write(p) + w.buf = append(w.buf, p...) + + if !w.closed { + if w.Writer != nil { + return w.Writer.Write(p) + } } - return n, err + + return len(p), nil } -func (w *CmdOutputWriter) Close() error { +func (w *CmdOutputWriter) Close(output ...[]byte) error { + defer w.dispatch() + + for _, s := range output { + w.write(true, s) + } + w.mu.Lock() defer w.mu.Unlock() @@ -56,13 +78,24 @@ func (w *CmdOutputWriter) Close() error { return nil } +func (w *CmdOutputWriter) dispatch() { + if w.Dispatch == nil { + return + } + + out := w.Output() + if len(out.Output) != 0 || out.Close { + w.Dispatch(out) + } +} + func (w *CmdOutputWriter) Output() CmdOutput { w.mu.Lock() defer w.mu.Unlock() - s := w.buf.Bytes() - w.buf.Reset() - return CmdOutput{Fd: w.Fd, Output: s, Close: w.closed} + out := CmdOutput{Fd: w.Fd, Output: w.buf, Close: w.closed} + w.buf = nil + return out } type CmdOutput struct { @@ -102,18 +135,40 @@ func (cs *cmdSupport) cmdOutput(mx *Ctx, out CmdOutput) *State { type RunCmd struct { ActionType - Fd string - Input bool - Name string - Args []string + Fd string + Input bool + Name string + Args []string + CancelID string } type Proc struct { - bx *BultinCmdCtx - mu sync.RWMutex - done chan struct{} - cmd *exec.Cmd - task *TaskTicket + bx *BultinCmdCtx + mu sync.RWMutex + done chan struct{} + closed bool + cmd *exec.Cmd + task *TaskTicket + cid string +} + +func newProc(bx *BultinCmdCtx) *Proc { + cmd := exec.Command(bx.Name, bx.Args...) + if bx.Input { + s, _ := bx.View.ReadAll() + cmd.Stdin = bytes.NewReader(s) + } + cmd.Dir = bx.View.Wd + cmd.Env = bx.Env.Environ() + cmd.Stdout = bx.Output + cmd.Stderr = bx.Output + cmd.SysProcAttr = pgSysProcAttr + return &Proc{ + done: make(chan struct{}), + bx: bx, + cmd: cmd, + cid: bx.CancelID, + } } func (p *Proc) Cancel() { @@ -123,9 +178,7 @@ func (p *Proc) Cancel() { select { case <-p.done: default: - if p := p.cmd.Process; p != nil { - p.Signal(os.Interrupt) - } + pgKill(p.cmd.Process) } } @@ -134,43 +187,46 @@ func (p *Proc) start() error { defer p.mu.Unlock() p.task = p.bx.Begin(Task{ - Title: fmt.Sprintf("RunCmd `%s`", mgutil.QuoteCmd(p.bx.Name, p.bx.Args...)), - Cancel: p.Cancel, + CancelID: p.cid, + Title: fmt.Sprintf("Proc`%s`", mgutil.QuoteCmd(p.bx.Name, p.bx.Args...)), + Cancel: p.Cancel, }) - go p.dispatchOutputLoop() - return p.cmd.Start() -} + go p.dispatcher() -func (p *Proc) dispatchOutput() { - p.mu.Lock() - defer p.mu.Unlock() - - out := p.bx.Output.Output() - if len(out.Output) != 0 || out.Close { - p.bx.Store.Dispatch(out) + if err := p.cmd.Start(); err != nil { + p.close() + return err } + return nil } -func (p *Proc) dispatchOutputLoop() { +func (p *Proc) dispatcher() { + defer p.task.Done() + for { select { case <-p.done: return case <-time.After(1 * time.Second): - p.dispatchOutput() + p.bx.Output.dispatch() } } } +func (p *Proc) close() { + if p.closed { + return + } + p.closed = true + close(p.done) +} + func (p *Proc) Wait() error { - defer p.dispatchOutput() defer func() { p.mu.Lock() defer p.mu.Unlock() - close(p.done) - p.bx.Output.Close() - p.task.Done() + p.close() }() return p.cmd.Wait() diff --git a/src/margo.sh/mg/cmd_nix.go b/src/margo.sh/mg/cmd_nix.go index 37ae84d9..fe69a7d7 100644 --- a/src/margo.sh/mg/cmd_nix.go +++ b/src/margo.sh/mg/cmd_nix.go @@ -3,11 +3,18 @@ package mg import ( + "os" "syscall" ) -func init() { - defaultSysProcAttr = &syscall.SysProcAttr{ - Setsid: true, +var ( + pgSysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } +) + +func pgKill(p *os.Process) { + if p != nil { + syscall.Kill(-p.Pid, syscall.SIGINT) } } diff --git a/src/margo.sh/mg/cmd_win.go b/src/margo.sh/mg/cmd_win.go new file mode 100644 index 00000000..62ba6c8e --- /dev/null +++ b/src/margo.sh/mg/cmd_win.go @@ -0,0 +1,18 @@ +// +build windows + +package mg + +import ( + "os" + "syscall" +) + +var ( + pgSysProcAttr *syscall.SysProcAttr +) + +func pgKill(p *os.Process) { + if p != nil { + p.Kill() + } +} diff --git a/src/margo.sh/mg/db.go b/src/margo.sh/mg/db.go index 6399efef..fae85674 100644 --- a/src/margo.sh/mg/db.go +++ b/src/margo.sh/mg/db.go @@ -1,7 +1,111 @@ package mg +import ( + "sync" +) + +var ( + _ KVStore = (*KVStores)(nil) + _ KVStore = (*Store)(nil) + _ KVStore = (*KVMap)(nil) +) + +// KVStore represents a generic key value store. +// +// All operations are safe for concurrent access. +// +// The main implementation in this and related packages is Store. type KVStore interface { - Put(k interface{}, v interface{}) - Get(k interface{}) interface{} - Del(k interface{}) + // Put stores the value in the store with identifier key + // NOTE: use Del instead of storing nil + Put(key, value interface{}) + + // Get returns the value stored using identifier key + // NOTE: if the value doesn't exist, nil is returned + Get(key interface{}) interface{} + + // Del removes the value identified by key from the store + Del(key interface{}) +} + +// KVStores implements a KVStore that duplicates its operations on a list of k/v stores +type KVStores []KVStore + +// Put calls .Put on each of k/v stores in the list +func (kvl KVStores) Put(k, v interface{}) { + for _, kvs := range kvl { + kvs.Put(k, v) + } +} + +// Get returns the first value identified by k found in the list of k/v stores +func (kvl KVStores) Get(k interface{}) interface{} { + for _, kvs := range kvl { + if v := kvs.Get(k); v != nil { + return v + } + } + return nil +} + +// Del removed the value identified by k from all k/v stores in the list +func (kvl KVStores) Del(k interface{}) { + for _, kvs := range kvl { + kvs.Del(k) + } +} + +// KVMap implements a KVStore using a map. +// The zero-value is safe for use with all operations. +type KVMap struct { + vals map[interface{}]interface{} + mu sync.RWMutex +} + +// Put implements KVStore.Put +func (m *KVMap) Put(k interface{}, v interface{}) { + m.mu.Lock() + defer m.mu.Unlock() + + if m.vals == nil { + m.vals = map[interface{}]interface{}{} + } + + m.vals[k] = v +} + +// Get implements KVStore.Get +func (m *KVMap) Get(k interface{}) interface{} { + m.mu.RLock() + defer m.mu.RUnlock() + + return m.vals[k] +} + +// Del implements KVStore.Del +func (m *KVMap) Del(k interface{}) { + m.mu.Lock() + defer m.mu.Unlock() + + delete(m.vals, k) +} + +// Clear removes all values from the store +func (m *KVMap) Clear() { + m.mu.Lock() + defer m.mu.Unlock() + + m.vals = map[interface{}]interface{}{} +} + +// Values returns a copy of all values stored +func (m *KVMap) Values() map[interface{}]interface{} { + m.mu.RLock() + defer m.mu.RUnlock() + + vals := make(map[interface{}]interface{}, len(m.vals)) + for k, v := range m.vals { + vals[k] = v + } + return vals } diff --git a/src/margo.sh/mg/state.go b/src/margo.sh/mg/state.go index e07452c7..63ff45ae 100644 --- a/src/margo.sh/mg/state.go +++ b/src/margo.sh/mg/state.go @@ -6,7 +6,6 @@ import ( "github.com/ugorji/go/codec" "go/build" "margo.sh/misc/pprof/pprofdo" - "os" "path/filepath" "reflect" "runtime" @@ -216,9 +215,9 @@ type State struct { UserCmds []UserCmd } -func newState() *State { +func newState(sto *Store) *State { return &State{ - stickyState: stickyState{View: newView()}, + stickyState: stickyState{View: newView(sto)}, } } @@ -345,37 +344,9 @@ func (cp *clientProps) finalize(ag *Agent) { ep.settings = ce.Settings } -func makeClientProps() clientProps { +func makeClientProps(kvs KVStore) clientProps { return clientProps{ Env: EnvMap{}, - View: &View{}, + View: newView(kvs), } } - -func (c *clientProps) updateCtx(mx *Ctx) *Ctx { - return mx.Copy(func(mx *Ctx) { - mx.State = mx.State.Copy(func(st *State) { - st.Editor = c.Editor.EditorProps - if c.Env != nil { - st.Env = c.Env - } - if c.View != nil { - st.View = c.View - // TODO: convert View.Pos to bytes - // at moment gocode is most affected, - // but to fix it here means we have to read the file off-disk - // so I'd rather not do that until we have some caching in place - } - if st.View != nil { - osGopath := os.Getenv("GOPATH") - fn := st.View.Filename() - for _, dir := range strings.Split(osGopath, string(filepath.ListSeparator)) { - if IsParentDir(dir, fn) { - st.Env = st.Env.Add("GOPATH", osGopath) - break - } - } - } - }) - }) -} diff --git a/src/margo.sh/mg/store.go b/src/margo.sh/mg/store.go index 86314afd..a01b6fe5 100644 --- a/src/margo.sh/mg/store.go +++ b/src/margo.sh/mg/store.go @@ -1,6 +1,9 @@ package mg import ( + "os" + "path/filepath" + "strings" "sync" ) @@ -31,6 +34,8 @@ func (sr storeReducers) Copy(updaters ...func(*storeReducers)) storeReducers { } type Store struct { + KVMap + mu sync.Mutex readyCh chan struct{} state *State @@ -47,7 +52,6 @@ type Store struct { sync.RWMutex vName string vHash string - m map[interface{}]interface{} } } @@ -99,14 +103,44 @@ func (sto *Store) syncRqAct(ag *Agent, props clientProps, ra agentReqAction) (*S mx, done := newCtx(sto.ag, sto.state, act, sto) defer close(done) - // TODO: add support for unpacking Action.Data + mx = mx.Copy(func(mx *Ctx) { + st := sto.prepState(mx.State) + + if ep := props.Editor.EditorProps; ep.Name != "" { + st.Editor = ep + } + + if len(props.Env) != 0 { + st.Env = props.Env + } + st.Env = sto.autoSwitchInternalGOPATH(st.View, st.Env) + + if props.View != nil && props.View.Name != "" { + st.View = props.View.Copy(func(v *View) { + sto.initCache(v) + v.initSrcPos() + }) + } + + mx.State = st + }) - mx = props.updateCtx(mx) - sto.initCache(mx.View) - mx.State = sto.prepState(mx.State) return sto.reducers.Reduce(mx), nil } +// autoSwitchInternalGOPATH automatically changes env[GOPATH] to the internal GOPATH +// if view.Filename is a child of one of the internal GOPATH directories +func (sto *Store) autoSwitchInternalGOPATH(v *View, env EnvMap) EnvMap { + osGopath := os.Getenv("GOPATH") + fn := v.Filename() + for _, dir := range strings.Split(osGopath, string(filepath.ListSeparator)) { + if IsParentDir(dir, fn) { + return env.Add("GOPATH", osGopath) + } + } + return env +} + func (sto *Store) updateState(st *State, callListener bool) *State { if callListener && sto.listener != nil { sto.listener(st) @@ -137,10 +171,9 @@ func newStore(ag *Agent, l Listener) *Store { sto := &Store{ readyCh: make(chan struct{}), listener: l, - state: newState(), + state: newState(ag.Store), ag: ag, } - sto.cache.m = map[interface{}]interface{}{} sto.tasks = newTaskTracker(sto.Dispatch) sto.After(sto.tasks) return sto @@ -214,28 +247,7 @@ func (sto *Store) initCache(v *View) { return } - cc.m = map[interface{}]interface{}{} + sto.KVMap.Clear() cc.vHash = v.Hash cc.vName = v.Name } - -func (sto *Store) Put(k interface{}, v interface{}) { - sto.cache.Lock() - defer sto.cache.Unlock() - - sto.cache.m[k] = v -} - -func (sto *Store) Get(k interface{}) interface{} { - sto.cache.RLock() - defer sto.cache.RUnlock() - - return sto.cache.m[k] -} - -func (sto *Store) Del(k interface{}) { - sto.cache.Lock() - defer sto.cache.Unlock() - - delete(sto.cache.m, k) -} diff --git a/src/margo.sh/mg/tasks.go b/src/margo.sh/mg/tasks.go index 6d0c2f82..a577668b 100644 --- a/src/margo.sh/mg/tasks.go +++ b/src/margo.sh/mg/tasks.go @@ -61,18 +61,90 @@ func (tr *taskTracker) Reduce(mx *Ctx) *State { tr.mu.Lock() defer tr.mu.Unlock() + st := mx.State switch mx.Action.(type) { case Started: tr.start() + case Shutdown: + for _, t := range tr.tickets { + t.Cancel() + } + case RunCmd: + st = st.AddBuiltinCmds(BultinCmd{ + Name: ".kill", + Desc: "List and cancel active tasks", + Run: tr.killBuiltin, + }) case taskTick: if len(tr.tickets) != 0 { tr.resetTimer() } } if s := tr.status(); s != "" { - return mx.AddStatus(s) + st = st.AddStatus(s) + } + return st +} + +// Cancel cancels the task tid. +// true is returned if the task exists and was canceled +func (tr *taskTracker) Cancel(tid string) bool { + tr.mu.Lock() + defer tr.mu.Unlock() + + return tr.cancel(tid) +} + +func (tr *taskTracker) cancel(tid string) bool { + for _, t := range tr.tickets { + if t.ID == tid || t.CancelID == tid { + t.Cancel() + return t.Cancellable() + } + } + return false +} + +func (tr *taskTracker) killBuiltin(bx *BultinCmdCtx) *State { + tr.mu.Lock() + defer tr.mu.Unlock() + + defer bx.Output.Close() + if len(bx.Args) == 0 { + tr.listAll(bx) + } else { + tr.killAll(bx) + } + + return bx.State +} + +func (tr *taskTracker) killAll(bx *BultinCmdCtx) { + buf := &bytes.Buffer{} + for _, tid := range bx.Args { + fmt.Fprintf(buf, "%s: %v\n", tid, tr.cancel(tid)) + } + bx.Output.Write(buf.Bytes()) +} + +func (tr *taskTracker) listAll(bx *BultinCmdCtx) { + buf := &bytes.Buffer{} + for _, t := range tr.tickets { + id := t.ID + if t.CancelID != "" { + id += "|" + t.CancelID + } + + dur := time.Since(t.Start) + if dur < time.Second { + dur = dur.Round(time.Millisecond) + } else { + dur = dur.Round(time.Second) + } + + fmt.Fprintf(buf, "ID: %s, Dur: %s, Title: %s\n", id, dur, t.Title) } - return mx.State + bx.Output.Write(buf.Bytes()) } func (tr *taskTracker) status() string { diff --git a/src/margo.sh/mg/view.go b/src/margo.sh/mg/view.go index dd7c059b..fd2dc79d 100644 --- a/src/margo.sh/mg/view.go +++ b/src/margo.sh/mg/view.go @@ -25,10 +25,11 @@ type View struct { Lang string changed int + kvs KVStore } -func newView() *View { - return &View{} +func newView(kvs KVStore) *View { + return &View{kvs: kvs} } func (v *View) Copy(updaters ...func(*View)) *View { @@ -65,9 +66,31 @@ func (v *View) Filename() string { return filepath.Join(v.Wd, v.Name) } +func (v *View) key() interface{} { + type Key struct{ Hash string } + return Key{v.Hash} +} + +func (v *View) src() (src []byte, ok bool) { + src = v.Src + if len(src) != 0 { + return src, true + } + + if v.kvs != nil { + src, _ = v.kvs.Get(v.key()).([]byte) + } + + if v.Path == "" || v.Dirty || len(src) != 0 { + return src, true + } + + return nil, false +} + func (v *View) ReadAll() ([]byte, error) { - if v.Dirty || len(v.Src) != 0 { - return v.Src, nil + if src, ok := v.src(); ok { + return src, nil } r, err := v.Open() @@ -76,16 +99,21 @@ func (v *View) ReadAll() ([]byte, error) { } defer r.Close() - return ioutil.ReadAll(r) + src, err := ioutil.ReadAll(r) + if err == nil && v.kvs != nil { + v.kvs.Put(v.key(), src) + } + + return src, err } func (v *View) Valid() bool { return v.Name != "" } -func (v *View) Open() (io.ReadCloser, error) { - if v.Dirty || len(v.Src) != 0 { - return ioutil.NopCloser(bytes.NewReader(v.Src)), nil +func (v *View) Open() (r io.ReadCloser, err error) { + if src, ok := v.src(); ok { + return ioutil.NopCloser(bytes.NewReader(src)), nil } if v.Path == "" { @@ -95,6 +123,18 @@ func (v *View) Open() (io.ReadCloser, error) { return os.Open(v.Path) } +func (v *View) initSrcPos() { + src, err := v.ReadAll() + if err != nil { + return + } + + v.Src = src + v.Pos = BytePos(src, v.Pos) + v.Hash = SrcHash(src) + v.kvs.Put(v.key(), src) +} + func (v *View) SetSrc(s []byte) *View { return v.Copy(func(v *View) { v.Pos = 0 diff --git a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go index 8c41cf6c..a1e08d7e 100644 --- a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go +++ b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go @@ -6,20 +6,22 @@ package blake2b -func init() { - useAVX2 = supportsAVX2() - useAVX = supportsAVX() - useSSE4 = supportsSSE4() -} +import _ "unsafe" -//go:noescape -func supportsSSE4() bool +//go:linkname x86_HasAVX internal/cpu.X86.HasAVX +var x86_HasAVX bool -//go:noescape -func supportsAVX() bool +//go:linkname x86_HasAVX2 internal/cpu.X86.HasAVX2 +var x86_HasAVX2 bool -//go:noescape -func supportsAVX2() bool +//go:linkname x86_HasAVX internal/cpu.X86.HasSSE4 +var x86_HasSSE4 bool + +func init() { + useAVX2 = x86_HasAVX2 + useAVX = x86_HasAVX + useSSE4 = x86_HasSSE4 +} //go:noescape func hashBlocksAVX2(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) diff --git a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s index 784bce6a..5593b1b3 100644 --- a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s +++ b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s @@ -748,15 +748,3 @@ noinc: MOVQ BP, SP RET - -// func supportsAVX2() bool -TEXT ·supportsAVX2(SB), 4, $0-1 - MOVQ runtime·support_avx2(SB), AX - MOVB AX, ret+0(FP) - RET - -// func supportsAVX() bool -TEXT ·supportsAVX(SB), 4, $0-1 - MOVQ runtime·support_avx(SB), AX - MOVB AX, ret+0(FP) - RET From f034f114eff8d946c1cfb6edfb14f6107012d5eb Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Sat, 14 Apr 2018 14:24:12 +0100 Subject: [PATCH 019/186] misc 9o improvements * navigating to any vfn should now work correctly * when output is written (before the comand ends), the view should no longer scroll * the `replay` command is now available in all views * when gs9o.py reloads, the view stash is no longer cleared so existing 9o view(s) should not be lost --- Default (Linux).sublime-keymap | 1 - Default (OSX).sublime-keymap | 1 - Default (Windows).sublime-keymap | 1 - gs9o.py | 47 +++++++++++++++++++------------- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Default (Linux).sublime-keymap b/Default (Linux).sublime-keymap index dcbd61c3..0f9d58dc 100644 --- a/Default (Linux).sublime-keymap +++ b/Default (Linux).sublime-keymap @@ -74,7 +74,6 @@ "keys": ["ctrl+.", "ctrl+r"], "command": "gs9o_open", "args": {"run": ["replay"], "focus_view": false}, - "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] }, { "keys": ["ctrl+.", "ctrl+g"], diff --git a/Default (OSX).sublime-keymap b/Default (OSX).sublime-keymap index 590e7091..3052c065 100644 --- a/Default (OSX).sublime-keymap +++ b/Default (OSX).sublime-keymap @@ -68,7 +68,6 @@ "keys": ["super+.", "super+r"], "command": "gs9o_open", "args": {"run": ["replay"], "focus_view": false}, - "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] }, { "keys": ["super+.", "super+g"], diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap index 532ec807..5471287e 100644 --- a/Default (Windows).sublime-keymap +++ b/Default (Windows).sublime-keymap @@ -74,7 +74,6 @@ "keys": ["ctrl+.", "ctrl+r"], "command": "gs9o_open", "args": {"run": ["replay"], "focus_view": false}, - "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] }, { "keys": ["ctrl+.", "ctrl+g"], diff --git a/gs9o.py b/gs9o.py index 46802677..f3d795fe 100644 --- a/gs9o.py +++ b/gs9o.py @@ -20,8 +20,8 @@ DOMAIN = "9o" AC_OPTS = sublime.INHIBIT_WORD_COMPLETIONS | sublime.INHIBIT_EXPLICIT_COMPLETIONS SPLIT_FN_POS_PAT = re.compile(r'(.+?)(?:[:](\d+))?(?:[:](\d+))?$') -URL_SCHEME_PAT = re.compile(r'^[\w.+-]+://') -URL_PATH_PAT = re.compile(r'^(?:[\w.+-]+://|(?:www|(?:\w+\.)*(?:golang|pkgdoc|gosublime)\.org))') +URL_SCHEME_PAT = re.compile(r'[\w.+-]+://') +URL_PATH_PAT = re.compile(r'(?:[\w.+-]+://|(?:www|(?:\w+\.)*(?:golang|pkgdoc|gosublime)\.org))') HIST_EXPAND_PAT = re.compile(r'^(\^+)\s*(\d+)$') HOURGLASS = u'\u231B' @@ -60,8 +60,15 @@ ] DEFAULT_CL = [(s, s+' ') for s in DEFAULT_COMMANDS] -stash = {} -tid_alias = {} +try: + stash +except NameError: + stash = {} + +try: + tid_alias +except NameError: + tid_alias = {} def active_wd(win=None): _, v = gs.win_view(win=win) @@ -342,17 +349,17 @@ def act_on_path(view, path): m = gs.VFN_ID_PAT.search(path) if m: path = 'gs.view://%s' % m.group(1) - m2 = gs.ROWCOL_PAT.match(m.group(2)) + m2 = gs.ROWCOL_PAT.search(m.group(2)) if m2: row = int(m2.group(1))-1 if m2.group(1) else 0 col = int(m2.group(2))-1 if m2.group(2) else 0 else: - if URL_PATH_PAT.match(path): + if URL_PATH_PAT.search(path): if path.lower().startswith('gs.packages://'): path = os.path.join(gs.packages_dir(), path[14:]) else: try: - if not URL_SCHEME_PAT.match(path): + if not URL_SCHEME_PAT.search(path): path = 'http://%s' % path gs.notify(DOMAIN, 'open url: %s' % path) webbrowser.open_new_tab(path) @@ -485,11 +492,11 @@ def run(self, edit, save_hist=False, env={}): view.insert(edit, gs.sel(view).begin(), '\n') class Gs9oPushOutput(sublime_plugin.TextCommand): - def run(self, edit, rkey, output, hourglass_repl='', done=True, append_only=False): + def run(self, edit, rkey, output, hourglass_repl='', done=True): view = self.view - if not append_only: - output = '\t%s' % gs.ustr(output).strip().replace('\r', '').replace('\n', '\n\t') - + output = '\t%s' % gs.ustr(output).strip().replace('\r', '').replace('\n', '\n\t') + xpos, vpos = view.viewport_position() + ypos = view.layout_extent()[1] - vpos regions = view.get_regions(rkey) if regions: prompt = view.line(regions[0].begin()) @@ -501,18 +508,22 @@ def run(self, edit, rkey, output, hourglass_repl='', done=True, append_only=Fals r = view.line(regions[-1].end()) if output.strip(): n = view.insert(edit, r.end(), '\n%s' % output) + r = sublime.Region(prompt.begin(), r.end() + n) view.erase_regions(rkey) - view.add_regions(rkey, [sublime.Region(prompt.begin(), r.end() + n)]) + view.add_regions(rkey, [r]) else: n = view.size() view.insert(edit, n, '\n%s' % output) r = sublime.Region(n, view.size()) - if not append_only and done: + if done: if gs.setting('9o_show_end') is True: - view.show(r.end()) + view.show(r.end(), True) else: - view.show(r.begin()) + view.show(r.begin(), True) + elif gs.sel(view).begin() >= r.begin(): + ypos = view.layout_extent()[1] - ypos + view.set_viewport_position((xpos, ypos), False) class Gs9oRunManyCommand(sublime_plugin.TextCommand): def run(self, edit, wd=None, commands=[], save_hist=False, focus_view=False, show_view=True): @@ -540,13 +551,12 @@ def builtins(): return m -def push_output(view, rkey, output, hourglass_repl='', done=True, append_only=False): +def push_output(view, rkey, output, hourglass_repl='', done=True): view.run_command('gs9o_push_output', { 'rkey': rkey, 'output': output, 'hourglass_repl': hourglass_repl, 'done': done, - 'append_only': append_only, }) def _save_all(win, wd): @@ -600,7 +610,6 @@ def _rcmd_output_handler(rs, act): 'rkey': rkey, 'output': act.output, 'done': act.close, - 'append_only': False, }, }) @@ -738,7 +747,7 @@ def cmd_run(view, edit, args, wd, rkey): def cmd_replay(view, edit, args, wd, rkey): _save_all(view.window(), wd) - _rcmd(view, edit, 'replay', args, wd, rkey) + _rcmd(view, edit, 'go.replay', args, wd, rkey) def cmd_build(view, edit, args, wd, rkey): cmd_9(view, edit, gs.lst('build', args), wd, rkey) From 8c94362c9656f4824e77f1176a8ef30e66dbd374 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Thu, 19 Apr 2018 10:47:23 +0100 Subject: [PATCH 020/186] sync margo --- src/margo.sh/golang/gocmd.go | 52 ++++++++++++++++++++++-------------- src/margo.sh/mg/builtins.go | 2 +- src/margo.sh/mg/issue.go | 3 ++- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/margo.sh/golang/gocmd.go b/src/margo.sh/golang/gocmd.go index 84fbbd4b..4cdf9485 100644 --- a/src/margo.sh/golang/gocmd.go +++ b/src/margo.sh/golang/gocmd.go @@ -146,18 +146,31 @@ func (gc *GoCmd) playToolTest(gx *goCmdCtx, bld *build.Context, origView *mg.Vie func (gc *GoCmd) playToolRun(gx *goCmdCtx, bld *build.Context, origView *mg.View) { args := gx.Args - exe := filepath.Join(gx.tDir, "margo.play~~"+filepath.Base(gx.tFn)+".exe") - gx.Name = "go" - gx.Args = []string{"build", "-o", exe} - gx.View = gx.View.Copy() - gx.View.Wd = gx.View.Dir() + exe := filepath.Join(gx.tDir, "margo.play~~"+filepath.Base(origView.Name)+".exe") + gx.BultinCmdCtx = gx.BultinCmdCtx.Copy(func(bx *mg.BultinCmdCtx) { + bx.Name = "go" + bx.Args = []string{"build", "-o", exe} + bx.Ctx = bx.Ctx.Copy(func(mx *mg.Ctx) { + mx.State = mx.State.Copy(func(st *mg.State) { + st.View = st.View.Copy(func(v *mg.View) { + v.Wd = v.Dir() + }) + }) + }) + }) if err := gx.run(origView); err != nil { return } - gx.View = origView - gx.Name = exe - gx.Args = args + gx.BultinCmdCtx = gx.BultinCmdCtx.Copy(func(bx *mg.BultinCmdCtx) { + bx.Name = exe + bx.Args = args + bx.Ctx = bx.Ctx.Copy(func(mx *mg.Ctx) { + mx.State = mx.State.Copy(func(st *mg.State) { + st.View = origView + }) + }) + }) gx.RunProc() } @@ -172,10 +185,9 @@ type goCmdCtx struct { func newGoCmdCtx(bx *mg.BultinCmdCtx, label, cancelID string, tDir, tFn string) *goCmdCtx { gx := &goCmdCtx{ - BultinCmdCtx: bx.Copy(), - pkgDir: bx.View.Dir(), - tDir: tDir, - tFn: tFn, + pkgDir: bx.View.Dir(), + tDir: tDir, + tFn: tFn, } type Key struct{ label string } @@ -184,16 +196,16 @@ func newGoCmdCtx(bx *mg.BultinCmdCtx, label, cancelID string, tDir, tFn string) gx.iw = &mg.IssueWriter{ Base: mg.Issue{Label: label}, Patterns: CommonPatterns, - Dir: gx.View.Wd, - } - if tDir != "" { - gx.iw.Dir = tDir + Dir: gx.pkgDir, } - gx.Name = "go" - gx.CancelID = cancelID - gx.Output = gx.Output.Copy() - gx.Output.Writer = gx.iw + gx.BultinCmdCtx = bx.Copy(func(bx *mg.BultinCmdCtx) { + bx.Name = "go" + bx.CancelID = cancelID + bx.Output = bx.Output.Copy(func(w *mg.CmdOutputWriter) { + w.Writer = gx.iw + }) + }) return gx } diff --git a/src/margo.sh/mg/builtins.go b/src/margo.sh/mg/builtins.go index 9a44a4c0..2356eec0 100644 --- a/src/margo.sh/mg/builtins.go +++ b/src/margo.sh/mg/builtins.go @@ -139,7 +139,7 @@ func (bx *BultinCmdCtx) RunProc() { err = p.Wait() } if err != nil { - fmt.Fprintf(bx.Output, "cannot run `%s`: %s\n", mgutil.QuoteCmd(bx.Name, bx.Args...), err) + fmt.Fprintf(bx.Output, "`%s` exited: %s\n", mgutil.QuoteCmd(bx.Name, bx.Args...), err) } } diff --git a/src/margo.sh/mg/issue.go b/src/margo.sh/mg/issue.go index 7d6eb871..8a806de2 100644 --- a/src/margo.sh/mg/issue.go +++ b/src/margo.sh/mg/issue.go @@ -159,7 +159,8 @@ func (iks *issueKeySupport) Reduce(mx *Ctx) *State { if name != "" && name == k.Name { return true } - if dir != "" && dir == k.Dir { + // if the view doesn't exist on disk, the dir is unreliable + if path != "" && dir != "" && dir == k.Dir { return true } return false From d928216bfbb6a9c1a881adc8332e0a020514a39c Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Wed, 2 May 2018 17:55:27 +0100 Subject: [PATCH 021/186] sync margo --- src/margo.sh/CONTRIBUTING.md | 39 ++ src/margo.sh/Gopkg.lock | 8 +- src/margo.sh/{LICENSE => LICENSE.md} | 2 +- src/margo.sh/README.md | 9 +- .../cmdpkg/margo/cmdrunner/cmdrunner.go | 2 + src/margo.sh/cmdpkg/margo/dev.go | 90 +++++ src/margo.sh/cmdpkg/margo/main.go | 7 +- .../cmdpkg/margosublime/main-extension.go | 1 - src/margo.sh/cmdpkg/margosublime/main.go | 5 +- .../extension-example/extension-example.go | 32 +- src/margo.sh/format/format.go | 4 +- src/margo.sh/golang/gocmd.go | 11 +- src/margo.sh/golang/gocode.go | 162 +++++--- src/margo.sh/golang/gocode_calltips.go | 73 +++- src/margo.sh/golang/gofmt.go | 4 +- .../golang/internal/gocode/bridge._margo_.go | 2 + src/margo.sh/golang/lint.go | 1 + src/margo.sh/golang/snippets.go | 28 +- src/margo.sh/golang/syntaxcheck.go | 45 +-- src/margo.sh/js/jsonfmt.go | 10 +- src/margo.sh/mg/action.go | 38 +- src/margo.sh/mg/agent.go | 125 ++++-- src/margo.sh/mg/agent_test.go | 111 ++++-- src/margo.sh/mg/builtins.go | 38 +- src/margo.sh/mg/builtins_test.go | 246 ++++++++++++ src/margo.sh/mg/cmd.go | 51 ++- src/margo.sh/mg/cmd_internal_test.go | 69 ++++ src/margo.sh/mg/cmd_test.go | 191 ++++++++++ src/margo.sh/mg/common.go | 67 ---- src/margo.sh/mg/db.go | 2 +- src/margo.sh/mg/issue.go | 9 +- src/margo.sh/mg/reducers.go | 220 ++++++++++- src/margo.sh/mg/state.go | 360 +++++++++++------- src/margo.sh/mg/state_test.go | 96 +++++ src/margo.sh/mg/store.go | 273 ++++++++----- src/margo.sh/mg/tasks.go | 94 +++-- src/margo.sh/mgutil/chanq.go | 62 +++ src/margo.sh/mgutil/chanq_test.go | 30 ++ src/margo.sh/mgutil/iowrapper.go | 65 ++++ src/margo.sh/mgutil/nocopy.go | 11 + src/margo.sh/mgutil/sync.go | 19 + src/margo.sh/misc/pf/pf.go | 191 ++++++++++ src/margo.sh/sublime/config.go | 2 +- src/margo.sh/sublime/ext.go | 10 +- .../x/crypto/blake2b/blake2bAVX2_amd64.go | 26 +- .../x/crypto/blake2b/blake2bAVX2_amd64.s | 12 + .../x/crypto/blake2b/blake2b_amd64.go | 7 +- .../x/crypto/blake2b/blake2b_amd64.s | 9 - src/margo.sh/vendor/golang.org/x/sys/AUTHORS | 3 + .../vendor/golang.org/x/sys/CONTRIBUTORS | 3 + src/margo.sh/vendor/golang.org/x/sys/LICENSE | 27 ++ src/margo.sh/vendor/golang.org/x/sys/PATENTS | 22 ++ .../vendor/golang.org/x/sys/cpu/cpu.go | 35 ++ .../vendor/golang.org/x/sys/cpu/cpu_arm.go | 7 + .../vendor/golang.org/x/sys/cpu/cpu_arm64.go | 7 + .../golang.org/x/sys/cpu/cpu_mips64x.go | 9 + .../vendor/golang.org/x/sys/cpu/cpu_mipsx.go | 9 + .../vendor/golang.org/x/sys/cpu/cpu_ppc64x.go | 9 + .../vendor/golang.org/x/sys/cpu/cpu_s390x.go | 7 + .../vendor/golang.org/x/sys/cpu/cpu_x86.go | 61 +++ .../vendor/golang.org/x/sys/cpu/cpu_x86.s | 26 ++ 61 files changed, 2575 insertions(+), 619 deletions(-) create mode 100644 src/margo.sh/CONTRIBUTING.md rename src/margo.sh/{LICENSE => LICENSE.md} (96%) create mode 100644 src/margo.sh/cmdpkg/margo/dev.go create mode 100644 src/margo.sh/mg/builtins_test.go create mode 100644 src/margo.sh/mg/cmd_internal_test.go create mode 100644 src/margo.sh/mg/cmd_test.go create mode 100644 src/margo.sh/mg/state_test.go create mode 100644 src/margo.sh/mgutil/chanq.go create mode 100644 src/margo.sh/mgutil/chanq_test.go create mode 100644 src/margo.sh/mgutil/iowrapper.go create mode 100644 src/margo.sh/mgutil/nocopy.go create mode 100644 src/margo.sh/mgutil/sync.go create mode 100644 src/margo.sh/misc/pf/pf.go create mode 100644 src/margo.sh/vendor/golang.org/x/sys/AUTHORS create mode 100644 src/margo.sh/vendor/golang.org/x/sys/CONTRIBUTORS create mode 100644 src/margo.sh/vendor/golang.org/x/sys/LICENSE create mode 100644 src/margo.sh/vendor/golang.org/x/sys/PATENTS create mode 100644 src/margo.sh/vendor/golang.org/x/sys/cpu/cpu.go create mode 100644 src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_arm.go create mode 100644 src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_arm64.go create mode 100644 src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_mips64x.go create mode 100644 src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_mipsx.go create mode 100644 src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_ppc64x.go create mode 100644 src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_s390x.go create mode 100644 src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_x86.go create mode 100644 src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_x86.s diff --git a/src/margo.sh/CONTRIBUTING.md b/src/margo.sh/CONTRIBUTING.md new file mode 100644 index 00000000..f4831e28 --- /dev/null +++ b/src/margo.sh/CONTRIBUTING.md @@ -0,0 +1,39 @@ +### Introduction + +Thank you for considering contributing to margo! + +Although margo is officially a Kuroku Labs product, it's still an open source project, and we welcome all types of contributions - be it bug reports, marketing, code, documentation, etc. + + +### Contributor License Agreement (CLA) + +As is the case with many Open Source projects, we can only accept source code contributions from contributors that have signed our CLA. Visit https://cla.kuroku.io/ for more details. + +### Dev environment setup + +The easiest way to get started with margo development is through GoSublime: + +* [install GoSublime](https://github.com/DisposaBoy/GoSublime#installation) with git +* switch to the `development` branch `git checkout development` +* while in Sublime Text/GoSublime, press ctrl+.,ctrl+9 (cmd+.,cmd+9 on Mac) to open the GoSublime command prompt and run the command `margo.sh dev fork $your-fork` e.g. `margo.sh dev fork git@github.com:DisposaBoy/margo.git` + + this sets the git remote `margo` to the upstream repo from which you will `pull` your updates. + the `origin` remote is set to your fork to which you will push your changes. + +### Your First Contribution + +Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). + +### Submitting code + +Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should ideally also contain tests. + +### Code review process + +The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge. +It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you? + + + + + diff --git a/src/margo.sh/Gopkg.lock b/src/margo.sh/Gopkg.lock index eac65e84..de10f658 100644 --- a/src/margo.sh/Gopkg.lock +++ b/src/margo.sh/Gopkg.lock @@ -17,7 +17,13 @@ branch = "master" name = "golang.org/x/crypto" packages = ["blake2b"] - revision = "d6449816ce06963d9d136eee5a56fca5b0616e7e" + revision = "2c241ca3045ddc354463c376a9515d9f1f1390a4" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["cpu"] + revision = "78d5f264b493f125018180c204871ecf58a2dce1" [solve-meta] analyzer-name = "dep" diff --git a/src/margo.sh/LICENSE b/src/margo.sh/LICENSE.md similarity index 96% rename from src/margo.sh/LICENSE rename to src/margo.sh/LICENSE.md index 68a72b33..411aedb0 100644 --- a/src/margo.sh/LICENSE +++ b/src/margo.sh/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 The MarGo Authors +Copyright (c) 2015 The margo Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/margo.sh/README.md b/src/margo.sh/README.md index 423b67b9..3cf26eac 100644 --- a/src/margo.sh/README.md +++ b/src/margo.sh/README.md @@ -2,7 +2,7 @@
-# MarGo +# margo This project is the next step in the evolution of https://github.com/DisposaBoy/GoSublime... this time with less Python, less Sublime Text and more Go. @@ -13,3 +13,10 @@ This repo is made public at this time solely to make it easier to integrate with It is under very active experimental development and code may be broken or deleted at any time. + +## License & Contributing + +margo is released under the MIT license. See [LICENSE.md](LICENSE.md) + +See [CONTRIBUTING.md](CONTRIBUTING.md) for details about contributing to the project. + diff --git a/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go b/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go index e401916d..5afee0e5 100644 --- a/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go +++ b/src/margo.sh/cmdpkg/margo/cmdrunner/cmdrunner.go @@ -12,6 +12,7 @@ type Cmd struct { Name string Args []string Env map[string]string + Dir string OutToErr bool } @@ -24,6 +25,7 @@ func (c Cmd) Run() error { } else { cmd.Stdout = os.Stdout } + cmd.Dir = c.Dir if len(c.Env) != 0 { environ := os.Environ() diff --git a/src/margo.sh/cmdpkg/margo/dev.go b/src/margo.sh/cmdpkg/margo/dev.go new file mode 100644 index 00000000..0c4bc6f4 --- /dev/null +++ b/src/margo.sh/cmdpkg/margo/dev.go @@ -0,0 +1,90 @@ +package margo + +import ( + "fmt" + "github.com/urfave/cli" + "go/build" + "margo.sh/cmdpkg/margo/cmdrunner" +) + +const ( + devRemoteFork = "origin" + devRemoteUpstream = "margo" + devUpstreamURL = "https://git.kuroku.io/margo" +) + +var ( + devCmd = cli.Command{ + Name: "dev", + Description: "", + Subcommands: cli.Commands{ + devCmdFork, + }, + } + + devCmdFork = cli.Command{ + Name: "fork", + Description: "set remote `" + devRemoteFork + "` to your fork and `" + devRemoteUpstream + "` to the margo.sh repo", + Action: func(cx *cli.Context) error { + pkg, err := devCmdFindPkgDir() + if err != nil { + return err + } + + args := cx.Args() + if len(args) != 1 { + return fmt.Errorf("Please specify the forked repo url") + } + + cmds := []cmdrunner.Cmd{ + cmdrunner.Cmd{ + Name: "git", + Args: []string{"remote", "add", "-f", devRemoteUpstream, devUpstreamURL}, + Dir: pkg.Dir, + OutToErr: true, + }, + cmdrunner.Cmd{ + Name: "git", + Args: []string{"remote", "set-url", "--push", devRemoteUpstream, "NoPushToMargoRepo"}, + Dir: pkg.Dir, + OutToErr: true, + }, + cmdrunner.Cmd{ + Name: "git", + Args: []string{"remote", "set-url", devRemoteFork, args[0]}, + Dir: pkg.Dir, + OutToErr: true, + }, + } + for _, cmd := range cmds { + e := cmd.Run() + if err == nil && e != nil { + err = e + } + } + return err + }, + } +) + +func devCmdFindPkgDir() (*build.Package, error) { + pkg, err := build.Import("margo.sh", ".", build.FindOnly) + if err == nil { + return pkg, nil + } + + err = cmdrunner.Cmd{ + Name: "go", + Args: []string{"get", "-d", "-v", "margo.sh"}, + OutToErr: true, + }.Run() + if err != nil { + return nil, fmt.Errorf("Cannot go get margo.sh: %s", err) + } + + pkg, err = build.Import("margo.sh", ".", 0) + if err != nil { + return nil, fmt.Errorf("Cannot find pkg dir: %s", err) + } + return pkg, nil +} diff --git a/src/margo.sh/cmdpkg/margo/main.go b/src/margo.sh/cmdpkg/margo/main.go index 01cf2a08..fee5ad87 100644 --- a/src/margo.sh/cmdpkg/margo/main.go +++ b/src/margo.sh/cmdpkg/margo/main.go @@ -59,7 +59,12 @@ func init() { func Main() { app := mgcli.NewApp() - app.Commands = []cli.Command{buildCmd, runCmd, startCmd} + app.Commands = []cli.Command{ + buildCmd, + runCmd, + startCmd, + devCmd, + } app.RunAndExitOnError() } diff --git a/src/margo.sh/cmdpkg/margosublime/main-extension.go b/src/margo.sh/cmdpkg/margosublime/main-extension.go index 178ebf83..7637d00a 100644 --- a/src/margo.sh/cmdpkg/margosublime/main-extension.go +++ b/src/margo.sh/cmdpkg/margosublime/main-extension.go @@ -9,5 +9,4 @@ import ( func init() { margoExt = margo.Margo - sublCfg = sublCfg.EnabledForLangs("*") } diff --git a/src/margo.sh/cmdpkg/margosublime/main.go b/src/margo.sh/cmdpkg/margosublime/main.go index 4c0fb974..90644935 100644 --- a/src/margo.sh/cmdpkg/margosublime/main.go +++ b/src/margo.sh/cmdpkg/margosublime/main.go @@ -9,8 +9,7 @@ import ( ) var ( - margoExt mg.MargoFunc = sublime.Margo - sublCfg mg.EditorConfig = sublime.DefaultConfig + margoExt mg.MargoFunc = sublime.Margo ) func Main() { @@ -34,7 +33,7 @@ func Main() { return mgcli.Error("agent creation failed:", err) } - ag.Store.EditorConfig(sublCfg) + ag.Store.EditorConfig(sublime.DefaultConfig) if margoExt != nil { margoExt(ag.Args()) } diff --git a/src/margo.sh/extension-example/extension-example.go b/src/margo.sh/extension-example/extension-example.go index 4db30f47..860aaba4 100644 --- a/src/margo.sh/extension-example/extension-example.go +++ b/src/margo.sh/extension-example/extension-example.go @@ -86,18 +86,22 @@ func Margo(ma mg.Args) { } // DayTimeStatus adds the current day and time to the status bar -var DayTimeStatus = mg.Reduce(func(mx *mg.Ctx) *mg.State { - if _, ok := mx.Action.(mg.Started); ok { - dispatch := mx.Store.Dispatch - // kick off the ticker when we start - go func() { - ticker := time.NewTicker(1 * time.Second) - for range ticker.C { - dispatch(mg.Render) - } - }() - } +type DayTimeStatus struct { + mg.ReducerType +} + +func (dts DayTimeStatus) ReducerMount(mx *mg.Ctx) { + // kick off the ticker when we start + dispatch := mx.Store.Dispatch + go func() { + ticker := time.NewTicker(1 * time.Second) + for range ticker.C { + dispatch(mg.Render) + } + }() +} +func (dts DayTimeStatus) Reduce(mx *mg.Ctx) *mg.State { // we always want to render the time // otherwise it will sometimes disappear from the status bar now := time.Now() @@ -106,10 +110,10 @@ var DayTimeStatus = mg.Reduce(func(mx *mg.Ctx) *mg.State { format = "Mon, 15 04" } return mx.AddStatus(now.Format(format)) -}) +} // MySnippets is a slice of functions returning our own snippets -var MySnippets = golang.SnippetFuncs{ +var MySnippets = golang.SnippetFuncs( func(cx *golang.CompletionCtx) []mg.Completion { // if we're not in a block (i.e. function), do nothing if !cx.Scope.Is(golang.BlockScope) { @@ -124,4 +128,4 @@ var MySnippets = golang.SnippetFuncs{ }, } }, -} +) diff --git a/src/margo.sh/format/format.go b/src/margo.sh/format/format.go index 82df8a2c..b2bd1226 100644 --- a/src/margo.sh/format/format.go +++ b/src/margo.sh/format/format.go @@ -38,7 +38,7 @@ func (ff FmtFunc) Reduce(mx *mg.Ctx) *mg.State { fn := mx.View.Filename() src, err := mx.View.ReadAll() if err != nil { - return mx.Errorf("failed to read %s: %s\n", fn, err) + return mx.AddErrorf("failed to read %s: %s\n", fn, err) } if len(src) == 0 { return mx.State @@ -46,7 +46,7 @@ func (ff FmtFunc) Reduce(mx *mg.Ctx) *mg.State { src, err = ff.Fmt(mx, src) if err != nil { - return mx.Errorf("failed to fmt %s: %s\n", fn, err) + return mx.AddErrorf("failed to fmt %s: %s\n", fn, err) } return mx.SetSrc(src) } diff --git a/src/margo.sh/golang/gocmd.go b/src/margo.sh/golang/gocmd.go index 4cdf9485..307e6486 100644 --- a/src/margo.sh/golang/gocmd.go +++ b/src/margo.sh/golang/gocmd.go @@ -10,7 +10,7 @@ import ( "strings" ) -type GoCmd struct{} +type GoCmd struct{ mg.ReducerType } func (gc *GoCmd) Reduce(mx *mg.Ctx) *mg.State { switch act := mx.Action.(type) { @@ -66,7 +66,14 @@ func (gc *GoCmd) playBuiltin(bx *mg.BultinCmdCtx) *mg.State { } func (gc *GoCmd) replayBuiltin(bx *mg.BultinCmdCtx) *mg.State { - go gc.playTool(bx, "go.replay") + v := bx.View + cid := "" + if v.Path == "" { + cid = v.Name + } else { + cid = v.Dir() + } + go gc.playTool(bx, "go.replay`"+cid+"`") return bx.State } diff --git a/src/margo.sh/golang/gocode.go b/src/margo.sh/golang/gocode.go index a5d1fbe6..5faa4896 100644 --- a/src/margo.sh/golang/gocode.go +++ b/src/margo.sh/golang/gocode.go @@ -11,8 +11,10 @@ import ( "io" "margo.sh/golang/internal/gocode" "margo.sh/mg" + "margo.sh/misc/pf" "margo.sh/sublime" "strings" + "time" "unicode" ) @@ -27,7 +29,28 @@ var ( } ) +type gocodeReq struct { + g *Gocode + mx *mg.Ctx + st *mg.State + gx *gocodeCtx + res chan *mg.State +} + +func (gr *gocodeReq) reduce() *mg.State { + candidates := gr.gx.candidates() + completions := make([]mg.Completion, 0, len(candidates)) + for _, v := range candidates { + if c, ok := gr.g.completion(gr.mx, gr.gx, v); ok { + completions = append(completions, c) + } + } + return gr.st.AddCompletions(completions...) +} + type Gocode struct { + mg.ReducerType + InstallSuffix string ProposeBuiltins bool ProposeTests bool @@ -37,25 +60,86 @@ type Gocode struct { AllowWordCompletions bool ShowFuncParams bool ShowFuncResultNames bool + Debug bool + + reqs chan gocodeReq +} + +func (g *Gocode) ReducerConfig(mx *mg.Ctx) mg.EditorConfig { + cfg, ok := mx.Config.(sublime.Config) + if !ok { + return nil + } + + cfg = cfg.DisableGsComplete() + if !g.AllowExplicitCompletions { + cfg = cfg.InhibitExplicitCompletions() + } + if !g.AllowWordCompletions { + cfg = cfg.InhibitWordCompletions() + } + return cfg +} + +func (g *Gocode) ReducerCond(mx *mg.Ctx) bool { + return mx.LangIs("go") && mx.ActionIs(mg.QueryCompletions{}) +} + +func (g *Gocode) ReducerMount(mx *mg.Ctx) { + g.reqs = make(chan gocodeReq) + go func() { + for gr := range g.reqs { + gr.res <- gr.reduce() + } + }() +} + +func (g *Gocode) ReducerUnmount(mx *mg.Ctx) { + close(g.reqs) } func (g *Gocode) Reduce(mx *mg.Ctx) *mg.State { - st, gx := initGocodeReducer(mx, g) - if gx == nil || !gx.query.completions { + start := time.Now() + st, gx := initGocodeReducer(mx, *g) + if gx == nil { return st } - candidates := gx.candidates() - completions := make([]mg.Completion, 0, len(candidates)) - for _, v := range candidates { - if c, ok := g.completion(mx, gx, v); ok { - completions = append(completions, c) - } + qTimeout := 100 * time.Millisecond + gr := gocodeReq{ + g: g, + mx: mx, + st: st, + gx: gx, + res: make(chan *mg.State, 1), + } + select { + case g.reqs <- gr: + case <-time.After(qTimeout): + mx.Log.Println("gocode didn't accept the request after", pf.D(time.Since(start))) + return st + } + + pTimeout := 150 * time.Millisecond + if d := qTimeout - time.Since(start); d > 0 { + pTimeout += d + } + + select { + case st := <-gr.res: + return st + case <-time.After(pTimeout): + go func() { + <-gr.res + mx.Log.Println("gocode eventually responded after", pf.Since(start)) + }() + + mx.Log.Println("gocode didn't respond after", pf.D(pTimeout), "taking", pf.Since(start)) + return st } - return st.AddCompletions(completions...) } -func (g *Gocode) funcTitle(fx *ast.FuncType, buf *bytes.Buffer, decl string) string { +func (g Gocode) funcTitle(fx *ast.FuncType, buf *bytes.Buffer, decl string) string { // TODO: caching buf.Reset() @@ -87,7 +171,7 @@ func (g *Gocode) funcTitle(fx *ast.FuncType, buf *bytes.Buffer, decl string) str return buf.String() } -func (g *Gocode) funcSrc(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate, gx *gocodeCtx) string { +func (g Gocode) funcSrc(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate, gx *gocodeCtx) string { // TODO: caching // TODO: only output the name, if we're in a call, assignment, etc. that takes a func @@ -143,7 +227,7 @@ func printFields(w io.Writer, fset *token.FileSet, list []*ast.Field, printNames } } -func (g *Gocode) completion(mx *mg.Ctx, gx *gocodeCtx, v gocode.MargoCandidate) (c mg.Completion, ok bool) { +func (g Gocode) completion(mx *mg.Ctx, gx *gocodeCtx, v gocode.MargoCandidate) (c mg.Completion, ok bool) { buf := bytes.NewBuffer(nil) if v.Class.String() == "PANIC" { mx.Log.Printf("gocode panicked in '%s' at pos '%d'\n", gx.fn, gx.pos) @@ -168,25 +252,25 @@ func (g *Gocode) completion(mx *mg.Ctx, gx *gocodeCtx, v gocode.MargoCandidate) return c, true } -func (g *Gocode) compQuery(v gocode.MargoCandidate) string { +func (g Gocode) compQuery(v gocode.MargoCandidate) string { return v.Name } -func (g *Gocode) compSrc(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate, gx *gocodeCtx) string { +func (g Gocode) compSrc(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate, gx *gocodeCtx) string { if fx == nil { return v.Name } return g.funcSrc(fx, buf, v, gx) } -func (g *Gocode) compTag(v gocode.MargoCandidate) mg.CompletionTag { +func (g Gocode) compTag(v gocode.MargoCandidate) mg.CompletionTag { if tag, ok := gocodeClassTags[v.Class.String()]; ok { return tag } return mg.UnknownTag } -func (g *Gocode) compTitle(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate) string { +func (g Gocode) compTitle(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate) string { if fx != nil { return g.funcTitle(fx, buf, v.Type) } @@ -196,7 +280,7 @@ func (g *Gocode) compTitle(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCa return v.Type } -func (g *Gocode) matchTests(c gocode.MargoCandidate) bool { +func (g Gocode) matchTests(c gocode.MargoCandidate) bool { return strings.HasPrefix(c.Name, "Test") || strings.HasPrefix(c.Name, "Benchmark") || strings.HasPrefix(c.Name, "Example") @@ -204,49 +288,22 @@ func (g *Gocode) matchTests(c gocode.MargoCandidate) bool { type gocodeCtx struct { Gocode - cn *CursorNode - fn string - src []byte - pos int - bctx *build.Context - cfg gocode.MargoConfig - query struct { - completions bool - tooltips bool - } + cn *CursorNode + fn string + src []byte + pos int + bctx *build.Context + cfg gocode.MargoConfig } -func initGocodeReducer(mx *mg.Ctx, g *Gocode) (*mg.State, *gocodeCtx) { +func initGocodeReducer(mx *mg.Ctx, g Gocode) (*mg.State, *gocodeCtx) { st := mx.State - if !st.View.LangIs("go") { - return st, nil - } - - if cfg, ok := st.Config.(sublime.Config); ok { - cfg = cfg.DisableGsComplete() - if !g.AllowExplicitCompletions { - cfg = cfg.InhibitExplicitCompletions() - } - if !g.AllowWordCompletions { - cfg = cfg.InhibitWordCompletions() - } - st = st.SetConfig(cfg) - } - - // TODO: use QueryCompletions.Pos when support is added - _, tooltips := mx.Action.(mg.QueryTooltips) - _, completions := mx.Action.(mg.QueryCompletions) - if !completions && !tooltips { - return st, nil - } - bctx := BuildContext(mx) src, _ := st.View.ReadAll() if len(src) == 0 { return st, nil } pos := clampSrcPos(src, st.View.Pos) - pos = mg.BytePos(src, pos) cx := NewCompletionCtx(mx, src, pos) if cx.Scope.Any(PackageScope, FileScope) { @@ -275,10 +332,9 @@ func initGocodeReducer(mx *mg.Ctx, g *Gocode) (*mg.State, *gocodeCtx) { ProposeBuiltins: g.ProposeBuiltins, Autobuild: g.Autobuild, UnimportedPackages: g.UnimportedPackages, + Debug: g.Debug, }, } - gx.query.completions = completions - gx.query.tooltips = tooltips return st, gx } diff --git a/src/margo.sh/golang/gocode_calltips.go b/src/margo.sh/golang/gocode_calltips.go index 3a16bac0..dad7d082 100644 --- a/src/margo.sh/golang/gocode_calltips.go +++ b/src/margo.sh/golang/gocode_calltips.go @@ -8,44 +8,79 @@ import ( "go/token" "margo.sh/golang/internal/gocode" "margo.sh/mg" + "margo.sh/mgutil" "margo.sh/sublime" "strings" ) -type GocodeCalltips struct{} +type gocodeCtAct struct { + mg.ActionType + mx *mg.Ctx + status string +} + +type GocodeCalltips struct { + mg.ReducerType + + q *mgutil.ChanQ + status string +} + +func (gc *GocodeCalltips) ReducerCond(mx *mg.Ctx) bool { + return mx.LangIs("go") +} + +func (gc *GocodeCalltips) ReducerMount(mx *mg.Ctx) { + gc.q = mgutil.NewChanQ(1) + go gc.processer() +} + +func (gc *GocodeCalltips) ReducerUnmount(mx *mg.Ctx) { + gc.q.Close() +} func (gc *GocodeCalltips) Reduce(mx *mg.Ctx) *mg.State { st := mx.State - if !mx.LangIs("go") { - return st - } if cfg, ok := st.Config.(sublime.Config); ok { st = st.SetConfig(cfg.DisableCalltips()) } - src, _ := mx.View.ReadAll() - if len(src) == 0 { - return st + switch act := mx.Action.(type) { + case mg.ViewPosChanged, mg.ViewActivated: + gc.q.Put(gocodeCtAct{mx: mx, status: gc.status}) + case gocodeCtAct: + gc.status = act.status } - type key struct{ hash string } - k := key{mg.SrcHash(src)} - - if mx.ActionIs(mg.ViewPosChanged{}, mg.ViewActivated{}) { - gc.process(mx, k, src) + if gc.status != "" { + return st.AddStatus(gc.status) } + return st +} - if s, _ := mx.Store.Get(k).(string); s != "" { - return st.AddStatus(s) +func (gc *GocodeCalltips) processer() { + for a := range gc.q.C() { + gc.process(a.(gocodeCtAct)) } - return st } -func (gc *GocodeCalltips) process(mx *mg.Ctx, key interface{}, src []byte) { - mx.Store.Del(key) +func (gc *GocodeCalltips) process(act gocodeCtAct) { + defer func() { recover() }() + + mx := act.mx + status := "" + defer func() { + if status != act.status { + mx.Store.Dispatch(gocodeCtAct{status: status}) + } + }() + + src, _ := mx.View.ReadAll() + if len(src) == 0 { + return + } srcPos := clampSrcPos(src, mx.View.Pos) - srcPos = mg.BytePos(src, srcPos) cn := ParseCursorNode(nil, src, srcPos) tpos := cn.TokenFile.Pos(srcPos) var call *ast.CallExpr @@ -93,7 +128,7 @@ func (gc *GocodeCalltips) process(mx *mg.Ctx, key interface{}, src []byte) { return } - mx.Store.Put(key, gc.funcSrc(fx, gc.argPos(call, tpos), funcName)) + status = gc.funcSrc(fx, gc.argPos(call, tpos), funcName) } func (gc *GocodeCalltips) funcSrc(fx *ast.FuncType, argPos int, funcName string) string { diff --git a/src/margo.sh/golang/gofmt.go b/src/margo.sh/golang/gofmt.go index 735c4404..402c996f 100644 --- a/src/margo.sh/golang/gofmt.go +++ b/src/margo.sh/golang/gofmt.go @@ -8,8 +8,8 @@ import ( ) var ( - GoFmt mg.Reducer = mg.Reduce(goFmt) - GoImports mg.Reducer = mg.Reduce(goImports) + GoFmt mg.Reducer = mg.NewReducer(goFmt) + GoImports mg.Reducer = mg.NewReducer(goImports) ) func disableGsFmt(st *mg.State) *mg.State { diff --git a/src/margo.sh/golang/internal/gocode/bridge._margo_.go b/src/margo.sh/golang/internal/gocode/bridge._margo_.go index f000801e..eb3c08ad 100644 --- a/src/margo.sh/golang/internal/gocode/bridge._margo_.go +++ b/src/margo.sh/golang/internal/gocode/bridge._margo_.go @@ -25,6 +25,7 @@ type MargoConfig struct { GOPATHS []string Autobuild bool UnimportedPackages bool + Debug bool } type margoEnv struct { @@ -51,6 +52,7 @@ func (m *margoEnv) assignConfig(gc *config, p *package_lookup_context, mc MargoC p.GOROOT = m.GOROOT p.GOPATH = m.GOPATH p.InstallSuffix = m.InstallSuffix + *g_debug = mc.Debug } type margoState struct { diff --git a/src/margo.sh/golang/lint.go b/src/margo.sh/golang/lint.go index 313ce714..26663f19 100644 --- a/src/margo.sh/golang/lint.go +++ b/src/margo.sh/golang/lint.go @@ -113,6 +113,7 @@ func (ls *linterSupport) lint(lo LinterOpts, dispatch mg.Dispatcher, st *mg.Stat } type Linter struct { + mg.ReducerType linterSupport Name string diff --git a/src/margo.sh/golang/snippets.go b/src/margo.sh/golang/snippets.go index 482d7bca..29c396e7 100644 --- a/src/margo.sh/golang/snippets.go +++ b/src/margo.sh/golang/snippets.go @@ -9,7 +9,7 @@ import ( ) var ( - Snippets = SnippetFuncs{ + Snippets = SnippetFuncs( PackageNameSnippet, MainFuncSnippet, InitFuncSnippet, @@ -18,20 +18,33 @@ var ( GenDeclSnippet, MapSnippet, TypeSnippet, - } + ) pkgDirNamePat = regexp.MustCompile(`(\w+)\W*$`) ) -type SnippetFuncs []func(*CompletionCtx) []mg.Completion +type SnippetFunc func(*CompletionCtx) []mg.Completion + +type SnippetFuncsList struct { + mg.ReducerType + Funcs []SnippetFunc +} + +func SnippetFuncs(l ...SnippetFunc) *SnippetFuncsList { + return &SnippetFuncsList{Funcs: l} +} -func (sf SnippetFuncs) Reduce(mx *mg.Ctx) *mg.State { +func (sf *SnippetFuncsList) Reduce(mx *mg.Ctx) *mg.State { if !mx.LangIs("go") || !mx.ActionIs(mg.QueryCompletions{}) { return mx.State } src, _ := mx.View.ReadAll() pos := mx.View.Pos + if pos < 0 || pos > len(src) { + return mx.State + } + for { r, n := utf8.DecodeLastRune(src[:pos]) if !IsLetter(r) { @@ -45,7 +58,7 @@ func (sf SnippetFuncs) Reduce(mx *mg.Ctx) *mg.State { } var cl []mg.Completion - for _, f := range sf { + for _, f := range sf.Funcs { cl = append(cl, f(cx)...) } for i, _ := range cl { @@ -54,7 +67,7 @@ func (sf SnippetFuncs) Reduce(mx *mg.Ctx) *mg.State { return mx.State.AddCompletions(cl...) } -func (sf SnippetFuncs) fixCompletion(c *mg.Completion) { +func (sf *SnippetFuncsList) fixCompletion(c *mg.Completion) { c.Src = DedentCompletion(c.Src) if c.Tag == "" { c.Tag = mg.SnippetTag @@ -290,9 +303,6 @@ func MethodSnippet(cx *CompletionCtx) []mg.Completion { return cl } -func (sf SnippetFuncs) name() { - -} func GenDeclSnippet(cx *CompletionCtx) []mg.Completion { if !cx.Scope.Is(FileScope) { diff --git a/src/margo.sh/golang/syntaxcheck.go b/src/margo.sh/golang/syntaxcheck.go index 9394ce83..68a014fa 100644 --- a/src/margo.sh/golang/syntaxcheck.go +++ b/src/margo.sh/golang/syntaxcheck.go @@ -1,42 +1,33 @@ package golang import ( - "margo.sh/mg" "go/scanner" + "margo.sh/mg" ) -type SyntaxCheck struct{} +type SyntaxCheck struct{ mg.ReducerType } func (sc *SyntaxCheck) Reduce(mx *mg.Ctx) *mg.State { - st := mx.State - if !st.View.LangIs("go") { - return st - } - - v := st.View - src, err := v.ReadAll() - if err != nil { - return st.Errorf("cannot read: %s: %s", v.Filename(), err) - } - - type key struct{ hash string } - k := key{mg.SrcHash(src)} - if issues, ok := mx.Store.Get(k).(mg.IssueSet); ok { - return st.AddIssues(issues...) - } - - if !mx.ActionIs(mg.ViewActivated{}, mg.ViewModified{}, mg.ViewSaved{}, mg.QueryIssues{}) { - return st + if mx.LangIs("go") && mx.ActionIs(mg.ViewActivated{}, mg.ViewModified{}, mg.ViewSaved{}) { + go sc.check(mx) } + return mx.State +} - pf := ParseFile(mx.Store, v.Filename(), src) - issues := sc.errsToIssues(v, pf.ErrorList) - mx.Store.Put(k, issues) - - return st.AddIssues(issues...) +func (sc *SyntaxCheck) check(mx *mg.Ctx) { + src, _ := mx.View.ReadAll() + pf := ParseFile(mx.Store, mx.View.Filename(), src) + type Key struct{} + mx.Store.Dispatch(mg.StoreIssues{ + Key: mg.IssueKey{ + Key: Key{}, + Name: mx.View.Name, + }, + Issues: sc.errsToIssues(mx.View, pf.ErrorList), + }) } -func (_ *SyntaxCheck) errsToIssues(v *mg.View, el scanner.ErrorList) mg.IssueSet { +func (sc *SyntaxCheck) errsToIssues(v *mg.View, el scanner.ErrorList) mg.IssueSet { issues := make(mg.IssueSet, len(el)) for i, e := range el { issues[i] = mg.Issue{ diff --git a/src/margo.sh/js/jsonfmt.go b/src/margo.sh/js/jsonfmt.go index e7a2f85f..680bdde8 100644 --- a/src/margo.sh/js/jsonfmt.go +++ b/src/margo.sh/js/jsonfmt.go @@ -1,11 +1,13 @@ package js import ( - "margo.sh/mg" "encoding/json" + "margo.sh/mg" ) type JsonFmt struct { + mg.ReducerType + Prefix string Indent string } @@ -21,18 +23,18 @@ func (j JsonFmt) Reduce(mx *mg.Ctx) *mg.State { fn := mx.View.Filename() r, err := mx.View.Open() if err != nil { - return mx.Errorf("failed to open %s: %s\n", fn, err) + return mx.AddErrorf("failed to open %s: %s\n", fn, err) } defer r.Close() var v interface{} if err := json.NewDecoder(r).Decode(&v); err != nil { - return mx.Errorf("failed to unmarshal json %s: %s\n", fn, err) + return mx.AddErrorf("failed to unmarshal json %s: %s\n", fn, err) } src, err := json.MarshalIndent(v, j.Prefix, j.Indent) if err != nil { - return mx.Errorf("failed to marshal json %s: %s\n", fn, err) + return mx.AddErrorf("failed to marshal json %s: %s\n", fn, err) } return mx.SetSrc(src) } diff --git a/src/margo.sh/mg/action.go b/src/margo.sh/mg/action.go index 1d4dffd6..ada21e39 100644 --- a/src/margo.sh/mg/action.go +++ b/src/margo.sh/mg/action.go @@ -5,6 +5,8 @@ import ( ) var ( + _ Action = actionType{} + actionCreators = map[string]actionCreator{ "QueryCompletions": func(codec.Handle, agentReqAction) (Action, error) { return QueryCompletions{}, nil @@ -12,9 +14,6 @@ var ( "QueryIssues": func(codec.Handle, agentReqAction) (Action, error) { return QueryIssues{}, nil }, - "QueryTooltips": func(codec.Handle, agentReqAction) (Action, error) { - return QueryTooltips{}, nil - }, "Restart": func(codec.Handle, agentReqAction) (Action, error) { return Restart{}, nil }, @@ -53,39 +52,36 @@ var ( } ) +// Started is dispatched to indicate the start of IPC communication. +// It's the first action that is dispatched. +type Started struct{ ActionType } + type actionCreator func(codec.Handle, agentReqAction) (Action, error) +type actionType struct{ ActionType } + type ActionType struct{} -func (act ActionType) Action() ActionType { - return act -} +func (act ActionType) actionType() actionType { return actionType{} } + +func (act ActionType) ActionLabel() string { return "" } type Action interface { - Action() ActionType + actionType() actionType + + ActionLabel() string } var Render Action = nil -// Started is dispatched to indicate the start of IPC communication. -// It's the first action that is dispatched. -// Reducers may do lazy initialization during this action. -type Started struct{ ActionType } - type QueryCompletions struct{ ActionType } type QueryIssues struct{ ActionType } -type QueryTooltips struct{ ActionType } - +// Restart is the action dispatched to initiate a graceful restart of the agent type Restart struct{ ActionType } -// Shutdown is the action dispatched when margo is shutting down. -// Reducers may use it as a signal to do any cleanups with the following caveats: -// * it might not be dispatched at all -// * it might be dispatched multiple times -// * IPC might not be available so state changes might have no effect -// * logging should, but might not, be available +// Shutdown is the action dispatched to initiate a graceful shutdown of the agent type Shutdown struct{ ActionType } type ViewActivated struct{ ActionType } @@ -101,3 +97,5 @@ type ViewPreSave struct{ ActionType } type ViewSaved struct{ ActionType } type ViewLoaded struct{ ActionType } + +type unmount struct{ ActionType } diff --git a/src/margo.sh/mg/agent.go b/src/margo.sh/mg/agent.go index 17a67b05..27f07301 100644 --- a/src/margo.sh/mg/agent.go +++ b/src/margo.sh/mg/agent.go @@ -5,10 +5,13 @@ import ( "fmt" "github.com/ugorji/go/codec" "io" + "margo.sh/mgutil" + "margo.sh/misc/pf" "os" "sort" "strings" "sync" + "time" ) var ( @@ -58,9 +61,18 @@ type AgentConfig struct { // Default: json Codec string - Stdin io.ReadCloser + // Stdin is the stream through which the client sends encoded request data + // It's closed when Agent.Run() returns + Stdin io.ReadCloser + + // Stdout is the stream through which the server (the Agent type) sends encoded responses + // It's closed when Agent.Run() returns Stdout io.WriteCloser - Stderr io.WriteCloser + + // Stderr is used for logging + // Clients are encouraged to leave it open until the process exits + // to allow for logging to keep working during process shutdown + Stderr io.Writer } type agentReqAction struct { @@ -72,13 +84,23 @@ type agentReq struct { Cookie string Actions []agentReqAction Props clientProps + Sent string + Profile *pf.Profile } func newAgentReq(kvs KVStore) *agentReq { - return &agentReq{Props: makeClientProps(kvs)} + return &agentReq{ + Props: makeClientProps(kvs), + Profile: pf.NewProfile(""), + } } func (rq *agentReq) finalize(ag *Agent) { + rq.Profile.SetName(rq.Cookie) + const layout = "2006-01-02T15:04:05.000000" + if t, err := time.ParseInLocation(layout, rq.Sent, time.UTC); err == nil { + rq.Profile.Sample("ipc|transport", time.Since(t)) + } rq.Props.finalize(ag) } @@ -90,14 +112,26 @@ type agentRes struct { func (rs agentRes) finalize() interface{} { out := struct { + _struct struct{} `codec:",omitempty"` + agentRes State struct { + _struct struct{} `codec:",omitempty"` + Profile, + Editor, + Env struct{} + State Config interface{} ClientActions []clientActionType } }{} + out.agentRes = rs + if rs.State == nil { + return out + } + out.State.State = *rs.State inSt := &out.State.State outSt := &out.State @@ -129,7 +163,7 @@ type Agent struct { stdin io.ReadCloser stdout io.WriteCloser - stderr io.WriteCloser + stderr io.Writer handle codec.Handle enc *codec.Encoder @@ -145,40 +179,42 @@ type Agent struct { closed bool } +// Run starts the Agent's event loop. It returns immediately on the first error. func (ag *Agent) Run() error { defer ag.shutdown() return ag.communicate() } func (ag *Agent) communicate() error { - ag.Log.Println("started") - ag.Store.dispatch(Started{}) - ag.Store.ready() + sto := ag.Store + unsub := sto.Subscribe(ag.sub) + defer unsub() + + sto.mount() for { - rq := newAgentReq(ag.Store) + rq := newAgentReq(sto) if err := ag.dec.Decode(rq); err != nil { if err == io.EOF { return nil } return fmt.Errorf("ipc.decode: %s", err) } + rq.finalize(ag) ag.handleReq(rq) } - return nil } func (ag *Agent) handleReq(rq *agentReq) { + rq.Profile.Push("queue.wait") ag.wg.Add(1) - defer ag.wg.Done() + ag.Store.dsp.hi <- func() { + defer ag.wg.Done() + rq.Profile.Pop() - // TODO: put this on a channel in the future. - // at the moment we lock the store and block new requests to maintain request/response order - // but decoding time could become a problem if we start sending large requests from the client - // we currently only have 1 client (GoSublime) that we also control so it's ok for now... - - ag.Store.syncRq(ag, rq) + ag.Store.handleReq(rq) + } } func (ag *Agent) createAction(ra agentReqAction, h codec.Handle) (Action, error) { @@ -188,8 +224,11 @@ func (ag *Agent) createAction(ra agentReqAction, h codec.Handle) (Action, error) return nil, fmt.Errorf("Unknown action: %s", ra.Name) } -func (ag *Agent) listener(st *State) { - err := ag.send(agentRes{State: st}) +func (ag *Agent) sub(mx *Ctx) { + err := ag.send(agentRes{ + State: mx.State, + Cookie: mx.Cookie, + }) if err != nil { ag.Log.Println("agent.send failed. shutting down ipc:", err) go ag.shutdown() @@ -204,6 +243,12 @@ func (ag *Agent) send(res agentRes) error { return ag.enc.Encode(res.finalize()) } +// shutdown sequence: +// * stop incoming requests +// * wait for all reqs to complete +// * tell reducers to unmount +// * stop outgoing responses +// * tell the world we're done func (ag *Agent) shutdown() { sd := &ag.sd sd.mu.Lock() @@ -214,22 +259,23 @@ func (ag *Agent) shutdown() { } sd.closed = true - // shutdown sequence: - // * stop incoming requests - // * wait for all reqs to complete - // * tell reducers we're shutting down - // * stop outgoing responses - // * tell the world we're done - // defers because we want *some* guarantee that all these steps will be taken defer close(sd.done) defer ag.stdout.Close() - defer ag.Store.dispatch(Shutdown{}) + defer ag.Store.unmount() defer ag.wg.Wait() defer ag.stdin.Close() } +// NewAgent returns a new Agent, initialised using the settings in cfg. +// If cfg.Codec is invalid (see CodecNames), `DefaultCodec` will be used as the +// codec and an error returned. +// An initialised, usable agent object is always returned. +// +// For tests, NewTestingAgent(), NewTestingStore() and NewTestingCtx() +// are preferred to creating a new agent directly func NewAgent(cfg AgentConfig) (*Agent, error) { + var err error done := make(chan struct{}) ag := &Agent{ Name: cfg.AgentName, @@ -249,31 +295,44 @@ func NewAgent(cfg AgentConfig) (*Agent, error) { if ag.stderr == nil { ag.stderr = os.Stderr } - ag.stdin = &LockedReadCloser{ReadCloser: ag.stdin} - ag.stdout = &LockedWriteCloser{WriteCloser: ag.stdout} - ag.stderr = &LockedWriteCloser{WriteCloser: ag.stderr} + ag.stdin = &mgutil.IOWrapper{ + Locker: &sync.Mutex{}, + Reader: ag.stdin, + Closer: ag.stdin, + } + ag.stdout = &mgutil.IOWrapper{ + Locker: &sync.Mutex{}, + Writer: ag.stdout, + Closer: ag.stdout, + } + ag.stderr = &mgutil.IOWrapper{ + Locker: &sync.Mutex{}, + Writer: ag.stderr, + } ag.Log = NewLogger(ag.stderr) - ag.Store = newStore(ag, ag.listener). + ag.Store = newStore(ag, ag.sub). Before(defaultReducers.before...). Use(defaultReducers.use...). After(defaultReducers.after...) if e := os.Getenv("MARGO_BUILD_ERROR"); e != "" { - ag.Store.Use(Reduce(func(mx *Ctx) *State { + ag.Store.Use(NewReducer(func(mx *Ctx) *State { return mx.AddStatus(e) })) } if ag.handle == nil { - return ag, fmt.Errorf("Invalid codec '%s'. Expected %s", cfg.Codec, CodecNamesStr) + err = fmt.Errorf("Invalid codec '%s'. Expected %s", cfg.Codec, CodecNamesStr) + ag.handle = codecHandles[DefaultCodec] } ag.encWr = bufio.NewWriter(ag.stdout) ag.enc = codec.NewEncoder(ag.encWr, ag.handle) ag.dec = codec.NewDecoder(bufio.NewReader(ag.stdin), ag.handle) - return ag, nil + return ag, err } +// Args returns a new copy of agent's Args. func (ag *Agent) Args() Args { return Args{ Store: ag.Store, diff --git a/src/margo.sh/mg/agent_test.go b/src/margo.sh/mg/agent_test.go index 8feba5ee..94dd6797 100644 --- a/src/margo.sh/mg/agent_test.go +++ b/src/margo.sh/mg/agent_test.go @@ -1,6 +1,8 @@ package mg import ( + "io" + "margo.sh/mgutil" "os" "strings" "testing" @@ -12,23 +14,35 @@ import ( // * logs should go to os.Stderr by default // * IPC communication should be done on os.Stdin and os.Stdout by default func TestDefaults(t *testing.T) { - ag, err := NewAgent(AgentConfig{}) + ag, err := NewAgent(AgentConfig{ + Codec: "invalidcodec", + }) + if err == nil { + t.Error("NewAgent() = (nil); want (error)") + } + if ag == nil { + t.Fatal("ag = (nil); want (*Agent)") + } + if ag.handle != codecHandles[DefaultCodec] { + t.Errorf("ag.handle = (%v), want (%v)", ag.handle, codecHandles[DefaultCodec]) + } + + ag, err = NewAgent(AgentConfig{}) if err != nil { - t.Errorf("agent creation failed: %s", err) - return + t.Fatalf("agent creation failed: %s", err) } - stdin := ag.stdin - if w, ok := stdin.(*LockedReadCloser); ok { - stdin = w.ReadCloser + var stdin io.Reader = ag.stdin + if w, ok := stdin.(*mgutil.IOWrapper); ok { + stdin = w.Reader } - stdout := ag.stdout - if w, ok := stdout.(*LockedWriteCloser); ok { - stdout = w.WriteCloser + var stdout io.Writer = ag.stdout + if w, ok := stdout.(*mgutil.IOWrapper); ok { + stdout = w.Writer } - stderr := ag.stderr - if w, ok := stderr.(*LockedWriteCloser); ok { - stderr = w.WriteCloser + var stderr io.Writer = ag.stderr + if w, ok := stderr.(*mgutil.IOWrapper); ok { + stderr = w.Writer } cases := []struct { @@ -45,14 +59,16 @@ func TestDefaults(t *testing.T) { } for _, c := range cases { - if c.expect != c.got { - t.Errorf("%s? expected '%v', got '%v'", c.name, c.expect, c.got) - } + t.Run(c.name, func(t *testing.T) { + if c.expect != c.got { + t.Errorf("expected '%v', got '%v'", c.expect, c.got) + } + }) } } func TestFirstAction(t *testing.T) { - nrwc := NopReadWriteCloser{ + nrwc := &mgutil.IOWrapper{ Reader: strings.NewReader("{}\n"), } ag, err := NewAgent(AgentConfig{ @@ -61,29 +77,66 @@ func TestFirstAction(t *testing.T) { Stderr: nrwc, }) if err != nil { - t.Errorf("agent creation failed: %s", err) - return + t.Fatalf("agent creation failed: %s", err) } actions := make(chan Action, 1) - ag.Store.Use(Reduce(func(mx *Ctx) *State { + ag.Store.Use(NewReducer(func(mx *Ctx) *State { select { case actions <- mx.Action: default: } return mx.State })) +} + +type readWriteCloseStub struct { + mgutil.IOWrapper + closed bool + CloseFunc func() error +} - // there is a small chance that some other package might dispatch an action - // before we're ready e.g. in init() - type impossibru struct{ ActionType } - ag.Store.Dispatch(impossibru{}) +func (r *readWriteCloseStub) Close() error { return r.CloseFunc() } - go ag.Run() - act := <-actions - switch act.(type) { - case Started: - default: - t.Errorf("Expected first action to be `%T`, but it was %T\n", Started{}, act) +func TestAgentShutdown(t *testing.T) { + nrc := &readWriteCloseStub{} + nwc := &readWriteCloseStub{} + nerrc := &readWriteCloseStub{} + nrc.CloseFunc = func() error { + nrc.closed = true + return nil + } + nwc.CloseFunc = func() error { + nwc.closed = true + return nil + } + nerrc.CloseFunc = func() error { + nerrc.closed = true + return nil + } + + ag, err := NewAgent(AgentConfig{ + Stdin: nrc, + Stdout: nwc, + Stderr: nerrc, + Codec: "msgpack", + }) + if err != nil { + t.Fatalf("agent creation: err = (%#v); want (nil)", err) + } + ag.Store = newStore(ag, ag.sub) + err = ag.Run() + if err != nil { + t.Fatalf("ag.Run() = (%#v); want (nil)", err) + } + + if !nrc.closed { + t.Error("nrc.Close() want not called") + } + if !nwc.closed { + t.Error("nwc.Close() want not called") + } + if !ag.sd.closed { + t.Error("ag.sd.closed = (true); want (false)") } } diff --git a/src/margo.sh/mg/builtins.go b/src/margo.sh/mg/builtins.go index 2356eec0..f7870d86 100644 --- a/src/margo.sh/mg/builtins.go +++ b/src/margo.sh/mg/builtins.go @@ -3,13 +3,12 @@ package mg import ( "bytes" "fmt" - "margo.sh/mgutil" "os" "sort" ) var ( - Builtins = BuiltinCmds{} + Builtins = builtins{} ) type BultinCmdList []BultinCmd @@ -31,14 +30,16 @@ func (bcl BultinCmdList) Lookup(name string) (cmd BultinCmd, found bool) { panic("internal error: the `.exec` BuiltinCmd is not defined") } -type BuiltinCmds struct{} +// BuiltinCmds implements various builtin commands. +type builtins struct{ ReducerType } -func (bc BuiltinCmds) ExecCmd(bx *BultinCmdCtx) *State { - go bc.execCmd(bx) +// ExecCmd implements the `.exec` builtin. +func (b builtins) ExecCmd(bx *BultinCmdCtx) *State { + go b.execCmd(bx) return bx.State } -func (bc BuiltinCmds) execCmd(bx *BultinCmdCtx) { +func (b builtins) execCmd(bx *BultinCmdCtx) { defer bx.Output.Close() if bx.Name == ".exec" { @@ -54,7 +55,10 @@ func (bc BuiltinCmds) execCmd(bx *BultinCmdCtx) { bx.RunProc() } -func (bc BuiltinCmds) TypeCmd(bx *BultinCmdCtx) *State { +// TypeCmd tries to find the bx.Args in commands, and writes the description of +// the commands into provided buffer. If the Args is empty, it uses all +// available commands. +func (b builtins) TypeCmd(bx *BultinCmdCtx) *State { cmds := bx.BuiltinCmds names := bx.Args if len(names) == 0 { @@ -74,7 +78,9 @@ func (bc BuiltinCmds) TypeCmd(bx *BultinCmdCtx) *State { return bx.State } -func (bc BuiltinCmds) EnvCmd(bx *BultinCmdCtx) *State { +// EnvCmd finds all environment variables corresponding to bx.Args into the +// bx.Output buffer. +func (b builtins) EnvCmd(bx *BultinCmdCtx) *State { buf := &bytes.Buffer{} names := bx.Args if len(names) == 0 { @@ -92,15 +98,17 @@ func (bc BuiltinCmds) EnvCmd(bx *BultinCmdCtx) *State { return bx.State } -func (bc BuiltinCmds) Commands() BultinCmdList { +// Commands returns a list of predefined commands. +func (b builtins) Commands() BultinCmdList { return []BultinCmd{ - BultinCmd{Name: ".env", Desc: "List env vars", Run: bc.EnvCmd}, - BultinCmd{Name: ".exec", Desc: "Run a command through os/exec", Run: bc.ExecCmd}, - BultinCmd{Name: ".type", Desc: "Lists all builtins or which builtin handles a command", Run: bc.TypeCmd}, + BultinCmd{Name: ".env", Desc: "List env vars", Run: b.EnvCmd}, + BultinCmd{Name: ".exec", Desc: "Run a command through os/exec", Run: b.ExecCmd}, + BultinCmd{Name: ".type", Desc: "Lists all builtins or which builtin handles a command", Run: b.TypeCmd}, } } -func (bc BuiltinCmds) Reduce(mx *Ctx) *State { +// Reduce adds the list of predefined builtins for the RunCmd. +func (bc builtins) Reduce(mx *Ctx) *State { if _, ok := mx.Action.(RunCmd); ok { return mx.State.AddBuiltinCmds(bc.Commands()...) } @@ -139,10 +147,12 @@ func (bx *BultinCmdCtx) RunProc() { err = p.Wait() } if err != nil { - fmt.Fprintf(bx.Output, "`%s` exited: %s\n", mgutil.QuoteCmd(bx.Name, bx.Args...), err) + fmt.Fprintf(bx.Output, "`%s` exited: %s\n", p.Title, err) } } +// StartProc creates a new Proc and starts the underlying process. +// It always returns an initialised Proc. func (bx *BultinCmdCtx) StartProc() (*Proc, error) { p := newProc(bx) return p, p.start() diff --git a/src/margo.sh/mg/builtins_test.go b/src/margo.sh/mg/builtins_test.go new file mode 100644 index 00000000..8b7b8f00 --- /dev/null +++ b/src/margo.sh/mg/builtins_test.go @@ -0,0 +1,246 @@ +package mg_test + +import ( + "bytes" + "io" + "margo.sh/mg" + "os" + "strings" + "testing" +) + +func TestBultinCmdList_Lookup(t *testing.T) { + t.Parallel() + exec := func() mg.BultinCmd { + r, _ := mg.Builtins.Commands().Lookup(".exec") + return r + }() + item := mg.BultinCmd{ + Name: "this name", + Desc: "description", + Run: func(*mg.BultinCmdCtx) *mg.State { return nil }, + } + tcs := []struct { + name string + bcl mg.BultinCmdList + input string + wantCmd mg.BultinCmd + wantFound bool + }{ + {"empty cmd list", mg.BultinCmdList{}, "nothing to find", exec, false}, + {"not found", mg.BultinCmdList{item}, "not found", exec, false}, + {"found", mg.BultinCmdList{item}, item.Name, item, true}, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + gotCmd, gotFound := tc.bcl.Lookup(tc.input) + // there is no way to compare functions, therefore we just check the names. + if gotCmd.Name != tc.wantCmd.Name { + t.Errorf("Lookup(): gotCmd = (%v); want (%v)", gotCmd, tc.wantCmd) + } + if gotFound != tc.wantFound { + t.Errorf("Lookup(): gotFound = (%v); want (%v)", gotFound, tc.wantFound) + } + }) + } +} + +// tests when the Args is empty, it should pick up the available BuiltinCmd(s). +func TestTypeCmdEmptyArgs(t *testing.T) { + t.Parallel() + item1 := mg.BultinCmd{Name: "this name", Desc: "this description"} + item2 := mg.BultinCmd{Name: "another one", Desc: "should appear too"} + buf := new(bytes.Buffer) + input := &mg.BultinCmdCtx{ + Ctx: &mg.Ctx{ + State: &mg.State{ + BuiltinCmds: mg.BultinCmdList{item1, item2}, + }, + }, + Output: &mg.CmdOutputWriter{ + Writer: buf, + Dispatch: nil, + }, + } + + if got := mg.Builtins.TypeCmd(input); got != input.State { + t.Errorf("TypeCmd() = %v, want %v", got, input.State) + } + out := buf.String() + for _, item := range []mg.BultinCmd{item1, item2} { + if !strings.Contains(out, item.Name) { + t.Errorf("buf.String() = (%s); want (%s) in it", out, item.Name) + } + if !strings.Contains(out, item.Desc) { + t.Errorf("buf.String() = (%s); want (%s) in it", out, item.Desc) + } + } +} + +func setupBultinCmdCtx(cmds mg.BultinCmdList, args []string, envMap mg.EnvMap, buf io.Writer) (*mg.BultinCmdCtx, func()) { + ctx := mg.NewTestingCtx(nil) + ctx.State = ctx.AddBuiltinCmds(cmds...) + ctx.Env = envMap + rc := mg.RunCmd{Args: args} + + cmd := mg.NewBultinCmdCtx(ctx, rc) + cmd.Output = &mg.CmdOutputWriter{ + Writer: buf, + Dispatch: nil, + } + return cmd, ctx.Cancel +} + +// tests when command is found, it should choose it. +func TestTypeCmdLookupCmd(t *testing.T) { + t.Parallel() + item1 := mg.BultinCmd{Name: "this name", Desc: "this description"} + item2 := mg.BultinCmd{Name: "another one", Desc: "should not appear"} + buf := new(bytes.Buffer) + input, cleanup := setupBultinCmdCtx(mg.BultinCmdList{item1, item2}, []string{item2.Name}, nil, buf) + defer cleanup() + + if got := mg.Builtins.TypeCmd(input); got != input.State { + t.Errorf("TypeCmd() = %v, want %v", got, input.State) + } + out := buf.String() + if strings.Contains(out, item1.Name) { + t.Errorf("buf.String() = (%s); didn't expect (%s) in it", out, item1.Name) + } + if strings.Contains(out, item1.Name) { + t.Errorf("buf.String() = (%s); didn't expect (%s) in it", out, item1.Name) + } + if !strings.Contains(out, item2.Name) { + t.Errorf("buf.String() = (%s); want (%s) in it", out, item2.Name) + } + if !strings.Contains(out, item2.Name) { + t.Errorf("buf.String() = (%s); want (%s) in it", out, item2.Name) + } +} + +type envPair struct { + key string + value string +} + +func setupEnvCmd(t *testing.T) ([]envPair, []mg.BultinCmd, func()) { + envs := []envPair{ + {"thiskey", "the value"}, + {"anotherkey", "another value"}, + } + items := make([]mg.BultinCmd, len(envs)) + for i, e := range envs { + if err := os.Setenv(e.key, e.value); err != nil { + t.Fatalf("cannot set environment values: %v", err) + } + items[i] = mg.BultinCmd{Name: e.key, Desc: "doesn't matter"} + } + cleanup := func() { + for _, e := range envs { + os.Unsetenv(e.key) + } + } + return envs, items, cleanup +} + +func TestEnvCmd(t *testing.T) { + t.Parallel() + envs, items, cleanup := setupEnvCmd(t) + defer cleanup() + + tcs := []struct { + name string + cmds mg.BultinCmdList + args []string + envs mg.EnvMap + wantEnvs []envPair + }{ + {"no cmd no env", mg.BultinCmdList{}, []string{}, nil, nil}, + { + "no cmd with env", + mg.BultinCmdList{}, + []string{}, + mg.EnvMap{"tAlbt": "gRuVbi", "wILHI": "XOmsUdw"}, + []envPair{{"tAlbt", "gRuVbi"}, {"wILHI", "XOmsUdw"}}, + }, + { + "one env pair", mg.BultinCmdList{items[0]}, + []string{items[0].Name}, + nil, + []envPair{envs[0]}, + }, + { + "multiple env pairs", + mg.BultinCmdList{items[0], items[1]}, + []string{items[0].Name, items[1].Name}, + nil, + []envPair{envs[0], envs[1]}, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + buf := new(bytes.Buffer) + input, cleanup := setupBultinCmdCtx(tc.cmds, tc.args, tc.envs, buf) + defer cleanup() + if got := mg.Builtins.EnvCmd(input); got == nil { + t.Error("EnvCmd() = (nil); want (*State)") + } + out := buf.String() + for _, e := range tc.wantEnvs { + if !strings.Contains(out, e.key) { + t.Errorf("buf.String() = (%s); want (%s) in it", out, e.key) + } + if !strings.Contains(out, e.value) { + t.Errorf("buf.String() = (%s); want (%s) in it", out, e.value) + } + } + }) + } +} + +func TestBuiltinCmdsReduce(t *testing.T) { + t.Parallel() + isIn := func(cmd mg.BultinCmd, haystack mg.BultinCmdList) bool { + for _, h := range haystack { + if h.Name == cmd.Name && h.Desc == cmd.Desc { + return true + } + } + return false + } + + item := mg.BultinCmd{Name: "qNgEYow", Desc: "YKjYxqMnt"} + ctx := &mg.Ctx{ + State: &mg.State{ + BuiltinCmds: mg.BultinCmdList{item}, + }, + } + + bc := mg.Builtins + state := bc.Reduce(ctx) + if state == nil { + t.Fatal("bc.Reduce() = nil, want *State") + } + for _, cmd := range bc.Commands() { + if isIn(cmd, state.BuiltinCmds) { + t.Errorf("didn't want %v in %v", cmd, bc.Commands()) + } + } + if !isIn(item, state.BuiltinCmds) { + t.Errorf("want %v in %v", item, bc.Commands()) + } + + ctx.Action = mg.RunCmd{} + state = bc.Reduce(ctx) + if state == nil { + t.Fatal("bc.Reduce() = nil, want *State") + } + for _, cmd := range bc.Commands() { + if !isIn(cmd, state.BuiltinCmds) { + t.Errorf("want %v in %v", cmd, bc.Commands()) + } + } + if !isIn(item, state.BuiltinCmds) { + t.Errorf("want %v in %v", item, bc.Commands()) + } +} diff --git a/src/margo.sh/mg/cmd.go b/src/margo.sh/mg/cmd.go index 935e5999..74ed8bc6 100644 --- a/src/margo.sh/mg/cmd.go +++ b/src/margo.sh/mg/cmd.go @@ -2,15 +2,16 @@ package mg import ( "bytes" - "fmt" "io" "margo.sh/mgutil" "os" "os/exec" + "path/filepath" "sync" "time" ) +// CmdOutputWriter writes a command output to the writer. type CmdOutputWriter struct { io.Writer io.Closer @@ -22,16 +23,20 @@ type CmdOutputWriter struct { closed bool } +// Copy applies updaters to a new copy of the writer. func (w *CmdOutputWriter) Copy(updaters ...func(*CmdOutputWriter)) *CmdOutputWriter { - p := *w - p.buf = append([]byte{}, w.buf...) - p.Writer = nil - p.Closer = nil - w = &p + w.mu.Lock() + defer w.mu.Unlock() + + p := &CmdOutputWriter{ + Fd: w.Fd, + Dispatch: w.Dispatch, + } + p.buf = append(p.buf, w.buf...) for _, f := range updaters { - f(w) + f(p) } - return w + return p } func (w *CmdOutputWriter) Write(p []byte) (int, error) { @@ -57,6 +62,9 @@ func (w *CmdOutputWriter) write(writeIfClosed bool, p []byte) (int, error) { return len(p), nil } +// Close writes provided output(s) and closes the writer. It returns +// os.ErrClosed if Close has already been called. If the Closer is not nil +// w.Closer.Close() method will be called. func (w *CmdOutputWriter) Close(output ...[]byte) error { defer w.dispatch() @@ -89,6 +97,8 @@ func (w *CmdOutputWriter) dispatch() { } } +// Output returns the data buffered from previous calls to w.Write() and clears +// the buffer. func (w *CmdOutputWriter) Output() CmdOutput { w.mu.Lock() defer w.mu.Unlock() @@ -106,7 +116,7 @@ type CmdOutput struct { Close bool } -type cmdSupport struct{} +type cmdSupport struct{ ReducerType } func (cs *cmdSupport) Reduce(mx *Ctx) *State { switch act := mx.Action.(type) { @@ -143,6 +153,8 @@ type RunCmd struct { } type Proc struct { + Title string + bx *BultinCmdCtx mu sync.RWMutex done chan struct{} @@ -163,11 +175,22 @@ func newProc(bx *BultinCmdCtx) *Proc { cmd.Stdout = bx.Output cmd.Stderr = bx.Output cmd.SysProcAttr = pgSysProcAttr + + name := filepath.Base(bx.Name) + args := make([]string, len(bx.Args)) + for i, s := range bx.Args { + if filepath.IsAbs(s) { + s = filepath.Base(s) + } + args[i] = s + } + return &Proc{ - done: make(chan struct{}), - bx: bx, - cmd: cmd, - cid: bx.CancelID, + Title: "`" + mgutil.QuoteCmd(name, args...) + "`", + done: make(chan struct{}), + bx: bx, + cmd: cmd, + cid: bx.CancelID, } } @@ -188,7 +211,7 @@ func (p *Proc) start() error { p.task = p.bx.Begin(Task{ CancelID: p.cid, - Title: fmt.Sprintf("Proc`%s`", mgutil.QuoteCmd(p.bx.Name, p.bx.Args...)), + Title: p.Title, Cancel: p.Cancel, }) go p.dispatcher() diff --git a/src/margo.sh/mg/cmd_internal_test.go b/src/margo.sh/mg/cmd_internal_test.go new file mode 100644 index 00000000..0f9ae259 --- /dev/null +++ b/src/margo.sh/mg/cmd_internal_test.go @@ -0,0 +1,69 @@ +package mg + +import ( + "testing" +) + +func TestCmdSupport_Reduce_noCalls(t *testing.T) { + type unknown struct{ ActionType } + cs := &cmdSupport{} + ctx := NewTestingCtx(nil) + defer ctx.Cancel() + + if state := cs.Reduce(ctx); state != ctx.State { + t.Errorf("cmdSupport.Reduce() = %v, want %v", state, ctx.State) + } + + ctx.Action = new(unknown) + if state := cs.Reduce(ctx); state != ctx.State { + t.Errorf("cmdSupport.Reduce() = %v, want %v", state, ctx.State) + } +} + +func TestCmdSupport_Reduce_withRunCmd(t *testing.T) { + var called bool + cs := &cmdSupport{} + ctx := NewTestingCtx(RunCmd{ + Fd: "rHX23", + Name: ".mytest", + }) + defer ctx.Cancel() + + ctx.State = ctx.AddBuiltinCmds(BultinCmd{ + Name: ".mytest", + Run: func(bx *BultinCmdCtx) *State { + called = true + return bx.State + }, + }) + + if state := cs.Reduce(ctx); state != ctx.State { + t.Errorf("cmdSupport.Reduce() = %v, want %v", state, ctx.State) + } + if !called { + t.Errorf("cs.Reduce(%v): cs.runCmd() wasn't called", ctx) + } +} + +func TestCmdSupport_Reduce_withCmdOutput(t *testing.T) { + var called bool + fd := "CIlZ7zBWHIAL" + cs := &cmdSupport{} + ctx := NewTestingCtx(nil) + defer ctx.Cancel() + + ctx.Action = CmdOutput{ + Fd: fd, + } + + state := cs.Reduce(ctx) + for _, c := range state.clientActions { + if d, ok := c.Data.(CmdOutput); ok && d.Fd == fd { + called = true + break + } + } + if !called { + t.Errorf("cs.Reduce(%v): cs.cmdOutput() wasn't called", ctx) + } +} diff --git a/src/margo.sh/mg/cmd_test.go b/src/margo.sh/mg/cmd_test.go new file mode 100644 index 00000000..484a666f --- /dev/null +++ b/src/margo.sh/mg/cmd_test.go @@ -0,0 +1,191 @@ +package mg_test + +import ( + "bytes" + "fmt" + "margo.sh/mg" + "os" + "testing" +) + +func TestCmdOutputWriter_Copy(t *testing.T) { + var called1, called2 bool + u1 := func(*mg.CmdOutputWriter) { called1 = true } + u2 := func(*mg.CmdOutputWriter) { called2 = true } + + w := &mg.CmdOutputWriter{} + w.Copy(u1, u2) + if !called1 { + t.Error("u1 wasn't called") + } + if !called2 { + t.Error("u2 wasn't called") + } +} + +var errNotEnoughSize = fmt.Errorf("not enough size") + +type limitWriter []byte + +func (l limitWriter) Write(p []byte) (n int, err error) { + n = copy(l, p) + if n < len(p) { + return n, errNotEnoughSize + } + return n, nil +} + +// sub-tests that their names end with `closed` tests the first condition of the +// function. Different sizes are provided to examine the returned error. Writes +// should not exceed the size of limitWriter, hence the wantSize. +func TestCmdOutputWriter_Write(t *testing.T) { + p := []byte("vbfh7H8 P8tSKkJrKKklBnktkOLChBW") + + tcs := []struct { + name string + size int // length of limitWriter + closed bool // will close the writer before assertions if set true. + wantSize int + err error + }{ + {"zero size", 0, false, 0, errNotEnoughSize}, + {"small size", len(p) / 2, false, len(p) / 2, errNotEnoughSize}, + {"full size", len(p), false, len(p), nil}, + {"larger size", len(p) * 2, false, len(p), nil}, + {"zero size closed", 0, true, 0, os.ErrClosed}, + {"small size closed", len(p) / 2, true, 0, os.ErrClosed}, + {"full size closed", len(p), true, 0, os.ErrClosed}, + {"larger size closed", len(p) * 2, true, 0, os.ErrClosed}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + buf := make(limitWriter, tc.size) + w := &mg.CmdOutputWriter{Writer: &buf} + if tc.closed { + err := w.Close() + if err != nil { + t.Errorf("w.Close() = %v, want nil", err) + return + } + } + + n, err := w.Write(p) + if err != tc.err { + t.Errorf("_, err = w.Write(%s): err = %v, want %v", string(p), err, tc.err) + } + if n != tc.wantSize { + t.Errorf("n, _ = w.Write(%s): n = %d, want %d", string(p), n, tc.wantSize) + } + readBytes := p[:tc.wantSize] + wroteBytes := buf[:tc.wantSize] + if !bytes.Equal(readBytes, wroteBytes) { + t.Errorf("wroteBytes = %v, want %v", wroteBytes, readBytes) + } + }) + } +} + +type writerStub struct { + writeFunc func([]byte) (int, error) + closeFunc func() error +} + +func (w *writerStub) Write(p []byte) (int, error) { return w.writeFunc(p) } +func (w *writerStub) Close() error { return w.closeFunc() } + +func TestCmdOutputWriter_Close_noOutput(t *testing.T) { + var ( + called bool + closed bool + errClosed = fmt.Errorf("close call") + ) + buf := &writerStub{ + writeFunc: func(p []byte) (int, error) { + called = true + return len(p), nil + }, + closeFunc: func() error { + closed = true + return errClosed + }, + } + + w := &mg.CmdOutputWriter{ + Writer: buf, + Closer: buf, + } + err := w.Close() + if err != errClosed { + t.Errorf("w.Close() = %v, want %v", err, errClosed) + } + if called { + t.Fatal("didn't expect to call Write") + } + if !closed { + t.Error("expected to call Close") + } + + err = w.Close() + if err == nil { + t.Errorf("w.Close() = nil, want %v", os.ErrClosed) + } +} + +func TestCmdOutputWriter_Close_withOutput(t *testing.T) { + o1 := []byte("rTuYF2ZPez1wDQNt4") + o2 := []byte("DJW63ZoTl") + out := make(chan []byte, 2) + buf := &writerStub{ + writeFunc: func(p []byte) (int, error) { + out <- p + return 0, nil + }, + } + w := &mg.CmdOutputWriter{Writer: buf} + err := w.Close(o1, o2) + if err != nil { + t.Errorf("w.Close() = %v, want nil", err) + } + for i := 0; i < 2; i++ { + select { + case p := <-out: + if !bytes.Equal(p, o1) && !bytes.Equal(p, o2) { + t.Errorf("got %v, want %v or %v", p, o1, o2) + } + default: + t.Errorf("expected writing %v or %v", o1, o2) + } + } +} + +func TestCmdOutputWriter_Output(t *testing.T) { + buf := new(bytes.Buffer) + p := []byte("1I9zyYlh98jq5PeW") + w := &mg.CmdOutputWriter{ + Fd: "DgJe9ymB0q", + Writer: buf, + } + if _, err := w.Write(p); err != nil { + t.Fatalf("_, err = w.Write(p); err = %v, want nil", err) + } + + o := w.Output() + if o.Fd != w.Fd { + t.Errorf("o.Fd = %s, want %s", o.Fd, w.Fd) + } + if !bytes.Equal(o.Output, buf.Bytes()) { + t.Errorf("o.Output = %s, want %s", string(o.Output), buf.String()) + } + if o.Close { + t.Error("o.Close = true, want false") + } + + if err := w.Close(); err != nil { + t.Fatalf("w.Close() = %v, want nil", err) + } + o = w.Output() + if !o.Close { + t.Error("o.Close = false, want true") + } +} diff --git a/src/margo.sh/mg/common.go b/src/margo.sh/mg/common.go index 253eb870..1e16b8d5 100644 --- a/src/margo.sh/mg/common.go +++ b/src/margo.sh/mg/common.go @@ -1,10 +1,8 @@ package mg import ( - "io" "path/filepath" "strings" - "sync" ) type StrSet []string @@ -75,71 +73,6 @@ func (e EnvMap) List(k string) []string { return strings.Split(e[k], string(filepath.ListSeparator)) } -type LockedWriteCloser struct { - io.WriteCloser - mu sync.Mutex -} - -func (w *LockedWriteCloser) Write(p []byte) (int, error) { - w.mu.Lock() - defer w.mu.Unlock() - - return w.WriteCloser.Write(p) -} - -func (w *LockedWriteCloser) Close() error { - w.mu.Lock() - defer w.mu.Unlock() - - return w.WriteCloser.Close() -} - -type LockedReadCloser struct { - io.ReadCloser - mu sync.Mutex -} - -func (r *LockedReadCloser) Read(p []byte) (int, error) { - r.mu.Lock() - defer r.mu.Unlock() - - return r.ReadCloser.Read(p) -} - -func (r *LockedReadCloser) Close() error { - r.mu.Lock() - defer r.mu.Unlock() - - return r.ReadCloser.Close() -} - -type NopReadWriteCloser struct { - io.Reader - io.Writer - io.Closer -} - -func (n NopReadWriteCloser) Read(p []byte) (int, error) { - if n.Reader != nil { - return n.Reader.Read(p) - } - return 0, io.EOF -} - -func (n NopReadWriteCloser) Write(p []byte) (int, error) { - if n.Writer != nil { - return n.Writer.Write(p) - } - return len(p), nil -} - -func (n NopReadWriteCloser) Close() error { - if n.Closer != nil { - return n.Closer.Close() - } - return nil -} - func IsParentDir(parentDir, childPath string) bool { p, err := filepath.Rel(parentDir, childPath) return err == nil && p != "." && !strings.HasPrefix(p, ".."+string(filepath.Separator)) diff --git a/src/margo.sh/mg/db.go b/src/margo.sh/mg/db.go index fae85674..60408af4 100644 --- a/src/margo.sh/mg/db.go +++ b/src/margo.sh/mg/db.go @@ -5,7 +5,7 @@ import ( ) var ( - _ KVStore = (*KVStores)(nil) + _ KVStore = (KVStores)(nil) _ KVStore = (*Store)(nil) _ KVStore = (*KVMap)(nil) ) diff --git a/src/margo.sh/mg/issue.go b/src/margo.sh/mg/issue.go index 8a806de2..19ba39b1 100644 --- a/src/margo.sh/mg/issue.go +++ b/src/margo.sh/mg/issue.go @@ -132,13 +132,16 @@ type IssueKey struct { } type issueKeySupport struct { + ReducerType issues map[IssueKey]IssueSet } +func (iks *issueKeySupport) ReducerMount(mx *Ctx) { + iks.issues = map[IssueKey]IssueSet{} +} + func (iks *issueKeySupport) Reduce(mx *Ctx) *State { switch act := mx.Action.(type) { - case Started: - iks.issues = map[IssueKey]IssueSet{} case StoreIssues: if len(act.Issues) == 0 { delete(iks.issues, act.Key) @@ -174,7 +177,7 @@ func (iks *issueKeySupport) Reduce(mx *Ctx) *State { return mx.State.AddIssues(issues...) } -type issueStatusSupport struct{} +type issueStatusSupport struct{ ReducerType } func (_ issueStatusSupport) Reduce(mx *Ctx) *State { if len(mx.Issues) == 0 { diff --git a/src/margo.sh/mg/reducers.go b/src/margo.sh/mg/reducers.go index 127d0eab..85f02c85 100644 --- a/src/margo.sh/mg/reducers.go +++ b/src/margo.sh/mg/reducers.go @@ -4,10 +4,14 @@ import ( "go/build" "os/exec" "path/filepath" + "reflect" + "runtime" "strings" ) var ( + _ Reducer = reducerType{} + defaultReducers = struct { before, use, after []Reducer }{ @@ -23,12 +27,213 @@ var ( } ) +// A Reducer is the main method of state transitions in margo. +// +// ReducerType can be embedded into struct types to implement all optional methods, +// i.e. the only required method is Reduce() +// +// NewReducer() can be used to convert a function to a reducer. +// +// For reducers that are backed by goroutines that are only interested +// in the *last* of some value e.g. *Ctx, mgutil.ChanQ might be of use. +type Reducer interface { + // Reduce takes as input a Ctx describing the current state of the world + // and an Action describing some action that happened. + // Based on this action, the reducer returns a new state of the world. + // + // Reducers are called sequentially in the order they were registered + // with Store.Before(), Store.Use() or Store.After(). + // + // A reducer should not call Store.State(). + // + // Reducers should complete their work as quickly as possible, + // ideally only updating the state and not doing any work in the reducer itself. + // + // If a reducer is slow it might block the editor UI because some actions like + // fmt'ing the view must wait for the new src before the user + // can continue editing or saving the file. + // + // e.g. during the ViewFmt or ViewPreSave action, a reducer that knows how to + // fmt the file might update the state to hold a fmt'd copy of the view's src. + // + // or it can implement a linter that kicks off a goroutine to try to compile + // a package when one of its files when the ViewSaved action is dispatched. + Reduce(*Ctx) *State + + // ReducerLabel returns a string that can be used to name the reducer + // in pf.Profile and other display scenarios + ReducerLabel() string + + // ReducerConfig is called on each reduction, before ReducerCond + // if it returns a new EditorConfig, it's equivalent to State.SetConfig() + // but is always run before ReducerCond() so is usefull for making sure + // configuration changes are always applied, even if Reduce() isn't called + ReducerConfig(*Ctx) EditorConfig + + // ReducerCond is called before Reduce is called + // if it returns false, Reduce is not called + // + // It can be used a pre-condition in combination with Reducer(Un)Mount + ReducerCond(*Ctx) bool + + // ReducerMount is called once, after the first time that ReducerCond returns true + ReducerMount(*Ctx) + + // ReducerUnmount is called when communication with the client will stop + // it is only called if ReducerMount was called + // + // It can be used to clean up any resources created in ReducerMount + // + // After this method is called, Reduce will never be called again + ReducerUnmount(*Ctx) + + reducerType() reducerType +} + +type reducerType struct{ ReducerType } + +func (rt reducerType) Reduce(mx *Ctx) *State { return mx.State } + +// ReducerType implements all optional methods of a reducer +type ReducerType struct{} + +func (rt ReducerType) reducerType() reducerType { return reducerType{} } + +// ReducerLabel implements Reducer.ReducerLabel +func (rt ReducerType) ReducerLabel() string { return "" } + +// ReducerCond implements Reducer.ReducerCond +func (rt ReducerType) ReducerCond(*Ctx) bool { return true } + +// ReducerConfig implements Reducer.ReducerConfig +func (rt ReducerType) ReducerConfig(*Ctx) EditorConfig { return nil } + +// ReducerMount implements Reducer.ReducerMount +func (rt ReducerType) ReducerMount(*Ctx) {} + +// ReducerUnmount implements Reducer.ReducerUnmount +func (rt ReducerType) ReducerUnmount(*Ctx) {} + +// reducerList is a slice of reducers +type reducerList []Reducer + +// callReducers calls the reducers in the slice in order. +func (rl reducerList) callReducers(mx *Ctx) *Ctx { + for _, r := range rl { + mx = rl.callReducer(mx, r) + } + return mx +} + +func (rl reducerList) callReducer(mx *Ctx, r Reducer) *Ctx { + defer mx.Profile.Push(ReducerLabel(r)).Pop() + if c := rl.crConfig(mx, r); c != nil { + mx = mx.SetState(mx.State.SetConfig(c)) + } + if !rl.crCond(mx, r) { + return mx + } + rl.crMount(mx, r) + if rl.crUnmount(mx, r) { + return mx + } + return rl.crReduce(mx, r) +} + +func (rl reducerList) crConfig(mx *Ctx, r Reducer) EditorConfig { + defer mx.Profile.Push("ReducerConfig").Pop() + return r.ReducerConfig(mx) +} + +func (rl reducerList) crCond(mx *Ctx, r Reducer) bool { + defer mx.Profile.Push("ReducerCond").Pop() + return r.ReducerCond(mx) +} + +func (rl reducerList) crMount(mx *Ctx, r Reducer) { + if mx.Store.mounted[r] { + return + } + + defer mx.Profile.Push("Mount").Pop() + mx.Store.mounted[r] = true + r.ReducerMount(mx) +} + +func (rl reducerList) crUnmount(mx *Ctx, r Reducer) bool { + if !mx.ActionIs(unmount{}) || !mx.Store.mounted[r] { + return false + } + defer mx.Profile.Push("Unmount").Pop() + delete(mx.Store.mounted, r) + r.ReducerUnmount(mx) + return true +} + +func (rl reducerList) crReduce(mx *Ctx, r Reducer) *Ctx { + defer mx.Profile.Push("Reduce").Pop() + return mx.SetState(r.Reduce(mx)) +} + +// Add adds new reducers to the list. It returns a new list. +func (rl reducerList) Add(reducers ...Reducer) reducerList { + return append(rl[:len(rl):len(rl)], reducers...) +} + +// ReduceFunc wraps a function to be used as a reducer +// New instances should ideally be created using the global NewReducer() function +type ReduceFunc struct { + ReducerType + + // Func is the function to be used for the reducer + Func func(*Ctx) *State + + // Label is an optional string that may be used as a name for the reducer. + // If unset, a name based on the Func type will be used. + Label string +} + +// ReducerLabel implements ReducerLabeler +func (rf *ReduceFunc) ReducerLabel() string { + if s := rf.Label; s != "" { + return s + } + nm := "" + if p := runtime.FuncForPC(reflect.ValueOf(rf.Func).Pointer()); p != nil { + nm = p.Name() + } + return "mg.Reduce(" + nm + ")" +} + +// Reduce implements the Reducer interface, delegating to ReduceFunc.Func +func (rf *ReduceFunc) Reduce(mx *Ctx) *State { + return rf.Func(mx) +} + +// NewReducer creates a new ReduceFunc +func NewReducer(f func(*Ctx) *State) *ReduceFunc { + return &ReduceFunc{Func: f} +} + +// ReducerLabel returns a label for the reducer r. +// It takes into account the ReducerLabeler interface. +func ReducerLabel(r Reducer) string { + if lbl := r.ReducerLabel(); lbl != "" { + return lbl + } + if t := reflect.TypeOf(r); t != nil { + return t.String() + } + return "mg.Reducer" +} + type rsBuildRes struct { ActionType issues IssueSet } type restartSupport struct { + ReducerType issues IssueSet } @@ -36,7 +241,7 @@ func (r *restartSupport) Reduce(mx *Ctx) *State { st := mx.State switch act := mx.Action.(type) { case ViewSaved: - go r.prepRestart(mx) + r.tryPrepRestart(mx) case Restart: mx.Log.Printf("%T action dispatched\n", mx.Action) st = mx.addClientActions(clientRestart) @@ -49,7 +254,13 @@ func (r *restartSupport) Reduce(mx *Ctx) *State { return st.AddIssues(r.issues...) } -func (_ restartSupport) prepRestart(mx *Ctx) { +func (r *restartSupport) tryPrepRestart(mx *Ctx) { + v := mx.View + hasSfx := strings.HasSuffix + if !hasSfx(v.Path, ".go") || hasSfx(v.Path, "_test.go") { + return + } + dir := filepath.ToSlash(mx.View.Dir()) if !filepath.IsAbs(dir) { return @@ -64,6 +275,11 @@ func (_ restartSupport) prepRestart(mx *Ctx) { return } + go r.prepRestart(mx, dir) +} + +func (r *restartSupport) prepRestart(mx *Ctx, dir string) { + pkg, _ := build.Default.ImportDir(dir, 0) if pkg == nil || pkg.Name == "" { return diff --git a/src/margo.sh/mg/state.go b/src/margo.sh/mg/state.go index 63ff45ae..185d5ad3 100644 --- a/src/margo.sh/mg/state.go +++ b/src/margo.sh/mg/state.go @@ -4,84 +4,117 @@ import ( "context" "fmt" "github.com/ugorji/go/codec" - "go/build" - "margo.sh/misc/pprof/pprofdo" - "path/filepath" + "margo.sh/misc/pf" "reflect" - "runtime" - "strings" + "sync" "time" ) var ( + // ErrNoSettings is the error returned from EditorProps.Settings() + // when there was no settings sent from the editor ErrNoSettings = fmt.Errorf("no editor settings") _ context.Context = (*Ctx)(nil) ) +// Ctx holds data about the current request/reduction. +// +// To create a new instance, use Store.NewCtx() +// +// NOTE: Ctx should be treated as readonly and users should not assign to any +// of its fields or the fields of any of its members. +// If a field must be updated, you should use one of the methods like Copy +// +// Unless a field is tagged with `mg.Nillable:"true"`, it will never be nil +// and if updated, no field should be set to nil type Ctx struct { + // State is the current state of the world *State - Action Action + // Action is the action that was dispatched. + // It's a hint telling reducers about some action that happened, + // e.g. that the view is about to be saved or that it was changed. + Action Action `mg.Nillable:"true"` + + // Store is the global store Store *Store + // Log is the global logger Log *Logger - Parent *Ctx - Values map[interface{}]interface{} - DoneC <-chan struct{} + Cookie string + + Profile *pf.Profile - handle codec.Handle + doneC chan struct{} + cancelOnce *sync.Once + handle codec.Handle } -func (_ *Ctx) Deadline() (time.Time, bool) { +// newCtx creates a new Ctx +// if st is nil, the state will be set to the equivalent of Store.state.new() +// if p is nil a new Profile will be created with cookie as its name +func newCtx(sto *Store, st *State, act Action, cookie string, p *pf.Profile) *Ctx { + if st == nil { + st = sto.state.new() + } + if p == nil { + p = pf.NewProfile(cookie) + } + return &Ctx{ + State: st, + Action: act, + Store: sto, + Log: sto.ag.Log, + Cookie: cookie, + Profile: p, + doneC: make(chan struct{}), + cancelOnce: &sync.Once{}, + handle: sto.ag.handle, + } +} + +// Deadline implements context.Context.Deadline +func (*Ctx) Deadline() (time.Time, bool) { return time.Time{}, false } +// Cancel cancels the ctx by arranging for the Ctx.Done() channel to be closed. +// Canceling this Ctx cancels all other Ctxs Copy()ed from it. +func (mx *Ctx) Cancel() { + mx.cancelOnce.Do(func() { + close(mx.doneC) + }) +} + +// Done implements context.Context.Done() func (mx *Ctx) Done() <-chan struct{} { - return mx.DoneC + return mx.doneC } -func (_ *Ctx) Err() error { - return nil +// Err implements context.Context.Err() +func (mx *Ctx) Err() error { + select { + case <-mx.Done(): + return context.Canceled + default: + return nil + } } +// Value implements context.Context.Value() but always returns nil func (mx *Ctx) Value(k interface{}) interface{} { - if v, ok := mx.Values[k]; ok { - return v - } - if mx.Parent != nil { - return mx.Parent.Value(k) - } return nil } +// AgentName returns the name of the agent if set +// if set, it's usually the agent name as used in the command `margo.sh [run...] $agent` func (mx *Ctx) AgentName() string { return mx.Store.ag.Name } -func newCtx(ag *Agent, st *State, act Action, sto *Store) (mx *Ctx, done chan struct{}) { - if st == nil { - panic("newCtx: state must not be nil") - } - if st == nil { - panic("newCtx: store must not be nil") - } - done = make(chan struct{}) - return &Ctx{ - State: st, - Action: act, - - Store: sto, - - Log: ag.Log, - - DoneC: done, - - handle: ag.handle, - }, done -} - +// ActionIs returns true if the type Ctx.Action is the same type as any of those in actions func (mx *Ctx) ActionIs(actions ...Action) bool { typ := reflect.TypeOf(mx.Action) for _, act := range actions { @@ -92,98 +125,55 @@ func (mx *Ctx) ActionIs(actions ...Action) bool { return false } +// LangIs is a wrapper around Ctx.View.Lang() func (mx *Ctx) LangIs(names ...string) bool { return mx.View.LangIs(names...) } +// Copy create a shallow copy of the Ctx. +// +// It applies the functions in updaters to the new object. +// Updating the new Ctx via these functions is preferred to assigning to the new object func (mx *Ctx) Copy(updaters ...func(*Ctx)) *Ctx { x := *mx - x.Parent = mx - if len(mx.Values) != 0 { - x.Values = make(map[interface{}]interface{}, len(mx.Values)) - for k, v := range mx.Values { - x.Values[k] = v - } - } - for _, f := range updaters { - f(&x) - } - return &x -} + mx = &x -func (mx *Ctx) Begin(t Task) *TaskTicket { - return mx.Store.Begin(t) -} - -type Reducer interface { - Reduce(*Ctx) *State -} - -type ReducerList []Reducer - -func (rl ReducerList) ReduceCtx(mx *Ctx) *Ctx { - for _, r := range rl { - var st *State - pprofdo.Do(mx, rl.labels(r), func(_ context.Context) { - st = r.Reduce(mx) - }) - mx = mx.Copy(func(mx *Ctx) { - mx.State = st - }) + for _, f := range updaters { + f(mx) } return mx } -func (rl ReducerList) labels(r Reducer) []string { - lbl := "" - if rf, ok := r.(ReduceFunc); ok { - lbl = rf.Label - } else { - lbl = reflect.TypeOf(r).String() - } - return []string{"margo.reduce", lbl} -} - -func (rl ReducerList) Reduce(mx *Ctx) *State { - return rl.ReduceCtx(mx).State -} - -func (rl ReducerList) Add(reducers ...Reducer) ReducerList { - return append(rl[:len(rl):len(rl)], reducers...) -} - -type ReduceFunc struct { - Func func(*Ctx) *State - Label string +func (mx *Ctx) SetState(st *State) *Ctx { + mx = mx.Copy() + mx.State = st + return mx } -func (rf ReduceFunc) Reduce(mx *Ctx) *State { - return rf.Func(mx) +func (mx *Ctx) SetView(v *View) *Ctx { + return mx.SetState(mx.State.SetView(v)) } -func Reduce(f func(*Ctx) *State) ReduceFunc { - _, fn, line, _ := runtime.Caller(1) - for _, gp := range strings.Split(build.Default.GOPATH, string(filepath.ListSeparator)) { - s := strings.TrimPrefix(fn, filepath.Clean(gp)+string(filepath.Separator)) - if s != fn { - fn = filepath.ToSlash(s) - break - } - } - return ReduceFunc{ - Func: f, - Label: fmt.Sprintf("%s:%d", fn, line), - } +// Begin stars a new task and returns its ticket +func (mx *Ctx) Begin(t Task) *TaskTicket { + return mx.Store.Begin(t) } +// EditorProps holds data about the text editor type EditorProps struct { - Name string + // Name is the name of the editor + Name string + + // Version is the editor's version Version string - handle codec.Handle + handle codec.Handle `mg.Nillable:"true"` settings codec.Raw } +// Settings unmarshals the internal settings sent from the editor into v. +// If no settings were sent, it returns ErrNoSettings, +// otherwise it returns any error from unmarshalling. func (ep *EditorProps) Settings(v interface{}) error { if ep.handle == nil || len(ep.settings) == 0 { return ErrNoSettings @@ -191,52 +181,133 @@ func (ep *EditorProps) Settings(v interface{}) error { return codec.NewDecoderBytes(ep.settings, ep.handle).Decode(v) } +// EditorConfig is the common interface between internally supported editors. +// +// The main implementation is `sublime.Config` type EditorConfig interface { + // EditorConfig returns data to be sent to the editor. EditorConfig() interface{} + + // EnabledForLangs is a hint to the editor listing the languages + // for which actions should be dispatched. + // + // To request actions for all languages, use `"*"` (the default) EnabledForLangs(langs ...string) EditorConfig } -type stickyState struct { - View *View - Env EnvMap +// StickyState is state that's persisted from one reduction to the next. +// It holds the current state of the editor. +// +// All fields are readonly and should only be assigned to during a call to State.Copy(). +// Child fields esp. View should not be assigned to. +type StickyState struct { + // View describes the current state of the view. + // When constructed correctly (through Store.NewCtx()), View is never nil. + View *View + + // Env holds environment variables sent from the editor. + // For "go" views in the "margo.sh" tree and "margo" package, + // "GOPATH" is set to the GOPATH that was used to build the agent. + Env EnvMap + + // Editor holds data about the editor Editor EditorProps -} + // Config holds config data for the editor to use + Config EditorConfig `mg.Nillable:"true"` +} + +// State holds data about the state of the editor, and transformations made by reducers +// +// All fields are readonly and should only be assigned to during a call to State.Copy() +// Methods on this object that return *State, return a new object. +// As an optimization/implementation details, the methods may choose to return +// the input state object if no updates are done. +// +// New instances can be obtained through Store.NewCtx() +// +// Except StickyState, all fields are cleared at the start of a new dispatch. +// Fields that to be present for some time, e.g. Status and Issues, +// Should be populated at each call to the reducer +// even if the action is not its primary action. +// e.g. for linters, they should kill off a goroutine to do a compilation +// after the file has been saved (ViewSaved) but always return its cached issues. +// +// If a reducer fails to return their state unless their primary action is dispatched +// it could result in flickering in the editor for visible elements like the status type State struct { - stickyState - Config EditorConfig - Status StrSet - Errors StrSet - Completions []Completion - Tooltips []Tooltip - Issues IssueSet + // StickyState holds the current state of the editor + StickyState + + // Status holds the list of status messages to show in the view + Status StrSet + + // Errors hold the list of error to display to the user + Errors StrSet + + // Completions holds the list of completions to show to the user + Completions []Completion + + // Issues holds the list of issues to present to the user + Issues IssueSet + + // BuiltinCmds holds the list of builtin commands. + // It's usually populated during the RunCmd action. + BuiltinCmds BultinCmdList + + // UserCmds holds the list of user commands. + // It's usually populated during the QueryUserCmds action. + UserCmds []UserCmd + + // clientActions is a list of client actions to dispatch in the editor clientActions []clientActionType - BuiltinCmds BultinCmdList - UserCmds []UserCmd } +// ActionLabel returns a label for the actions act. +// It takes into account mg.Render being an alias for nil. +func ActionLabel(act Action) string { + t := reflect.TypeOf(act) + if t != nil { + if s := act.ActionLabel(); s != "" { + return s + } + return t.String() + } + return "mg.Render" +} + +// newState create a new State object ensuring View is initialized correctly. func newState(sto *Store) *State { return &State{ - stickyState: stickyState{View: newView(sto)}, + StickyState: StickyState{View: newView(sto)}, } } +// new creates a new State sharing State.StickyState func (st *State) new() *State { - return &State{stickyState: st.stickyState} + return &State{StickyState: st.StickyState} } +// Copy create a shallow copy of the State. +// +// It applies the functions in updaters to the new object. +// Updating the new State via these functions is preferred to assigning to the new object func (st *State) Copy(updaters ...func(*State)) *State { x := *st + st = &x + for _, f := range updaters { - f(&x) + f(st) } - return &x + return st } +// AddStatusf is equivalent to State.AddStatus(fmt.Sprintf()) func (st *State) AddStatusf(format string, a ...interface{}) *State { return st.AddStatus(fmt.Sprintf(format, a...)) } +// AddStatus adds the list of messages in l to State.Status. func (st *State) AddStatus(l ...string) *State { if len(l) == 0 { return st @@ -246,10 +317,12 @@ func (st *State) AddStatus(l ...string) *State { }) } -func (st *State) Errorf(format string, a ...interface{}) *State { +// AddErrorf is equivalent to State.AddError(fmt.Sprintf()) +func (st *State) AddErrorf(format string, a ...interface{}) *State { return st.AddError(fmt.Errorf(format, a...)) } +// AddError adds the non-nil errors in l to State.Errors. func (st *State) AddError(l ...error) *State { if len(l) == 0 { return st @@ -263,30 +336,44 @@ func (st *State) AddError(l ...error) *State { }) } +// SetConfig updates the State.Config. func (st *State) SetConfig(c EditorConfig) *State { return st.Copy(func(st *State) { st.Config = c }) } +// SetSrc is a wrapper around View.SetSrc(). +// If `len(src) == 0` it does nothing because this is almost always a bug. func (st *State) SetSrc(src []byte) *State { + if len(src) == 0 { + return st + } return st.Copy(func(st *State) { st.View = st.View.SetSrc(src) }) } -func (st *State) AddCompletions(l ...Completion) *State { - return st.Copy(func(st *State) { - st.Completions = append(st.Completions[:len(st.Completions):len(st.Completions)], l...) - }) +func (st *State) SetView(v *View) *State { + if st.View == v { + return st + } + st = st.Copy() + st.View = v + return st } -func (st *State) AddTooltips(l ...Tooltip) *State { +// AddCompletions adds the completions in l to State.Completions +func (st *State) AddCompletions(l ...Completion) *State { + if len(l) == 0 { + return st + } return st.Copy(func(st *State) { - st.Tooltips = append(st.Tooltips[:len(st.Tooltips):len(st.Tooltips)], l...) + st.Completions = append(st.Completions[:len(st.Completions):len(st.Completions)], l...) }) } +// AddIssues adds the list of issues in l to State.Issues func (st *State) AddIssues(l ...Issue) *State { if len(l) == 0 { return st @@ -296,6 +383,7 @@ func (st *State) AddIssues(l ...Issue) *State { }) } +// AddBuiltinCmds adds the list of builtin commands in l to State.BuiltinCmds func (st *State) AddBuiltinCmds(l ...BultinCmd) *State { if len(l) == 0 { return st @@ -305,6 +393,7 @@ func (st *State) AddBuiltinCmds(l ...BultinCmd) *State { }) } +// AddUserCmds adds the list of user commands in l to State.userCmds func (st *State) AddUserCmds(l ...UserCmd) *State { if len(l) == 0 { return st @@ -314,6 +403,7 @@ func (st *State) AddUserCmds(l ...UserCmd) *State { }) } +// addClientActions adds the list of client actions in l to State.clientActions func (st *State) addClientActions(l ...clientAction) *State { if len(l) == 0 { return st diff --git a/src/margo.sh/mg/state_test.go b/src/margo.sh/mg/state_test.go new file mode 100644 index 00000000..71dc1180 --- /dev/null +++ b/src/margo.sh/mg/state_test.go @@ -0,0 +1,96 @@ +package mg + +import ( + "io" + "margo.sh/mgutil" + "reflect" + "testing" + "time" +) + +// NewTestingAgent creates a new agent for testing +// +// The agent config used is equivalent to: +// * Codec: DefaultCodec +// * Stdin: stdin or &mgutil.IOWrapper{} if nil +// * Stdout: stdout or &mgutil.IOWrapper{} if nil +// * Stderr: &mgutil.IOWrapper{} +func NewTestingAgent(stdout io.WriteCloser, stdin io.ReadCloser) *Agent { + if stdout == nil { + stdout = &mgutil.IOWrapper{} + } + if stdin == nil { + stdin = &mgutil.IOWrapper{} + } + ag, _ := NewAgent(AgentConfig{ + Stdout: stdout, + Stdin: stdin, + Stderr: &mgutil.IOWrapper{}, + }) + return ag +} + +// NewTestingStore creates a new Store for testing +// It's equivalent to NewTestingAgent().Store +func NewTestingStore() *Store { + return NewTestingAgent(nil, nil).Store +} + +// NewTestingCtx creates a new Ctx for testing +// It's equivalent to NewTestingStore().NewCtx() +func NewTestingCtx(act Action) *Ctx { + return NewTestingStore().NewCtx(act) +} + +func checkNonNil(v interface{}, handler func(sel string)) { + checkNonNilVal(reflect.ValueOf(v), "", handler) +} + +func checkNonNilVal(v reflect.Value, sel string, handler func(sel string)) { + switch kind := v.Kind(); { + case kind == reflect.Ptr && !v.IsNil(): + checkNonNilVal(v.Elem(), sel, handler) + case kind == reflect.Struct: + typ := v.Type() + switch typ { + case reflect.TypeOf(time.Time{}): + return + } + + for i := 0; i < v.NumField(); i++ { + f := v.Field(i) + t := typ.Field(i) + switch f.Kind() { + case reflect.Struct: + checkNonNilVal(f, sel+"."+t.Name, handler) + case reflect.Ptr, reflect.Interface: + if f.IsNil() && t.Tag.Get(`mg.Nillable`) != "true" { + handler(sel + "." + t.Name) + } + } + } + } +} + +// TestNonNilFields checks that NewAgent() doesn't return a Agent, Store, +// State, Ctx or View that has nil fields that are not tagged `mg.Nillable:"true"` +func TestNewAgentNillableFields(t *testing.T) { + ag := NewTestingAgent(nil, nil) + mx := ag.Store.NewCtx(nil) + cases := []interface{}{ + ag, + ag.Store, + mx, + mx.State, + mx.State.View, + } + + for _, c := range cases { + name := reflect.TypeOf(c).String() + t.Run(name, func(t *testing.T) { + checkNonNil(c, func(sel string) { + t.Errorf("(%s)%s is nil but is not tagged mg.Nillable", name, sel) + }) + }) + } +} diff --git a/src/margo.sh/mg/store.go b/src/margo.sh/mg/store.go index a01b6fe5..f29fdf69 100644 --- a/src/margo.sh/mg/store.go +++ b/src/margo.sh/mg/store.go @@ -1,6 +1,7 @@ package mg import ( + "margo.sh/misc/pf" "os" "path/filepath" "strings" @@ -11,19 +12,27 @@ var _ Dispatcher = (&Store{}).Dispatch type Dispatcher func(Action) -type Listener func(*State) +type Subscriber func(*Ctx) + +type dispatchHandler func() type storeReducers struct { - before ReducerList - use ReducerList - after ReducerList + before reducerList + use reducerList + after reducerList } -func (sr storeReducers) Reduce(mx *Ctx) *State { - mx = sr.before.ReduceCtx(mx) - mx = sr.use.ReduceCtx(mx) - mx = sr.after.ReduceCtx(mx) - return mx.State +func (sr storeReducers) Reduce(mx *Ctx) *Ctx { + mx.Profile.Do("Before", func() { + mx = sr.before.callReducers(mx) + }) + mx.Profile.Do("Use", func() { + mx = sr.use.callReducers(mx) + }) + mx.Profile.Do("After", func() { + mx = sr.after.callReducers(mx) + }) + return mx } func (sr storeReducers) Copy(updaters ...func(*storeReducers)) storeReducers { @@ -36,16 +45,15 @@ func (sr storeReducers) Copy(updaters ...func(*storeReducers)) storeReducers { type Store struct { KVMap - mu sync.Mutex - readyCh chan struct{} - state *State - listeners []*struct{ Listener } - listener Listener - reducers struct { + mu sync.Mutex + state *State + subs []*struct{ Subscriber } + sub Subscriber + reducers struct { sync.Mutex storeReducers } - cfg EditorConfig + cfg EditorConfig `mg.Nillable:"true"` ag *Agent tasks *taskTracker cache struct { @@ -53,83 +61,158 @@ type Store struct { vName string vHash string } + + dsp struct { + sync.RWMutex + lo chan dispatchHandler + hi chan dispatchHandler + unmounted bool + } + + mounted map[Reducer]bool } -func (sto *Store) ready() { - close(sto.readyCh) +func (sto *Store) mount() { + go sto.dispatcher() +} + +func (sto *Store) unmount() { + done := make(chan struct{}) + sto.dsp.hi <- func() { + defer close(done) + + sto.dsp.Lock() + defer sto.dsp.Unlock() + + if sto.dsp.unmounted { + return + } + sto.dsp.unmounted = true + + sto.handleAct(unmount{}, nil) + } + <-done } func (sto *Store) Dispatch(act Action) { - go func() { - <-sto.readyCh - sto.dispatch(act) - }() + sto.dsp.lo <- func() { sto.handleAct(act, nil) } } -func (sto *Store) dispatch(act Action) { - sto.mu.Lock() - defer sto.mu.Unlock() +func (sto *Store) nextDispatcher() dispatchHandler { + var h dispatchHandler + select { + case h = <-sto.dsp.hi: + default: + select { + case h = <-sto.dsp.hi: + case h = <-sto.dsp.lo: + } + } - mx, done := newCtx(sto.ag, sto.prepState(sto.state), act, sto) - defer close(done) - st := sto.reducers.Reduce(mx) - sto.updateState(st, true) + sto.dsp.RLock() + defer sto.dsp.RUnlock() + + if sto.dsp.unmounted { + return nil + } + return h } -func (sto *Store) syncRq(ag *Agent, rq *agentReq) { - sto.mu.Lock() - defer sto.mu.Unlock() +func (sto *Store) dispatcher() { + sto.handleAct(Started{}, nil) + sto.ag.Log.Println("started") - rs := agentRes{Cookie: rq.Cookie, State: sto.state} - for _, ra := range rq.Actions { - st, err := sto.syncRqAct(ag, rq.Props, ra) - if st != nil { - sto.state = st // normally sto.updateState would do this - rs.State = st - } - if err != nil { - rs.Error = err.Error() + for { + if f := sto.nextDispatcher(); f != nil { + f() + } else { + return } } - rs.State = sto.updateState(rs.State, false) - ag.send(rs) } -func (sto *Store) syncRqAct(ag *Agent, props clientProps, ra agentReqAction) (*State, error) { - act, err := ag.createAction(ra, ag.handle) - if err != nil { - return nil, err +func (sto *Store) handleReduce(mx *Ctx) *Ctx { + defer mx.Profile.Push("action|" + ActionLabel(mx.Action)).Pop() + + return sto.reducers.Reduce(mx) +} + +func (sto *Store) handle(h func() *Ctx, p *pf.Profile) { + p.Push("handleRequest") + sto.mu.Lock() + + mx := h() + sto.state = mx.State + subs := sto.subs + + sto.mu.Unlock() + p.Pop() + + for _, p := range subs { + p.Subscriber(mx) } - mx, done := newCtx(sto.ag, sto.state, act, sto) - defer close(done) +} - mx = mx.Copy(func(mx *Ctx) { - st := sto.prepState(mx.State) +func (sto *Store) handleAct(act Action, p *pf.Profile) { + if p == nil { + p = pf.NewProfile("") + } + sto.handle(func() *Ctx { + mx := newCtx(sto, nil, act, "", p) + return sto.handleReduce(mx) + }, p) +} - if ep := props.Editor.EditorProps; ep.Name != "" { - st.Editor = ep +func (sto *Store) handleReq(rq *agentReq) { + sto.handle(func() *Ctx { + newMx := func(st *State, act Action) *Ctx { + return newCtx(sto, st, act, rq.Cookie, rq.Profile) } - - if len(props.Env) != 0 { - st.Env = props.Env + mx, acts := sto.handleReqInit(rq, newMx(nil, nil)) + for _, act := range acts { + st := mx.State.new() + st.Errors = mx.State.Errors + mx = newMx(st, act) + mx = sto.handleReduce(mx) } - st.Env = sto.autoSwitchInternalGOPATH(st.View, st.Env) + return mx + }, rq.Profile) +} - if props.View != nil && props.View.Name != "" { - st.View = props.View.Copy(func(v *View) { - sto.initCache(v) - v.initSrcPos() - }) - } +func (sto *Store) handleReqInit(rq *agentReq, mx *Ctx) (*Ctx, []Action) { + defer mx.Profile.Push("init").Pop() - mx.State = st - }) + acts := make([]Action, 0, len(rq.Actions)) + for _, ra := range rq.Actions { + act, err := sto.ag.createAction(ra, sto.ag.handle) + if err != nil { + mx.State = mx.AddErrorf("createAction(%s): %s", ra.Name, err) + } else { + acts = append(acts, act) + } + } - return sto.reducers.Reduce(mx), nil + if cfg := sto.cfg; cfg != nil { + mx.Config = cfg + } + props := rq.Props + if ep := props.Editor.EditorProps; ep.Name != "" { + mx.Editor = ep + } + if v := props.View; v != nil && v.Name != "" { + mx.View = v + sto.initCache(v) + v.initSrcPos() + } + if len(props.Env) != 0 { + mx.Env = props.Env + } + mx.Env = sto.autoSwitchInternalGOPATH(mx.View, mx.Env) + return mx, acts } // autoSwitchInternalGOPATH automatically changes env[GOPATH] to the internal GOPATH -// if view.Filename is a child of one of the internal GOPATH directories +// if v.Filename is a child of one of the internal GOPATH directories func (sto *Store) autoSwitchInternalGOPATH(v *View, env EnvMap) EnvMap { osGopath := os.Getenv("GOPATH") fn := v.Filename() @@ -141,17 +224,6 @@ func (sto *Store) autoSwitchInternalGOPATH(v *View, env EnvMap) EnvMap { return env } -func (sto *Store) updateState(st *State, callListener bool) *State { - if callListener && sto.listener != nil { - sto.listener(st) - } - for _, p := range sto.listeners { - p.Listener(st) - } - sto.state = st - return st -} - func (sto *Store) State() *State { sto.mu.Lock() defer sto.mu.Unlock() @@ -159,44 +231,51 @@ func (sto *Store) State() *State { return sto.state } -func (sto *Store) prepState(st *State) *State { - st = st.new() - if sto.cfg != nil { - st.Config = sto.cfg - } - return st +// NewCtx returns a new Ctx initialized using the internal StickyState. +// The caller is responsible for calling Ctx.Cancel() when done with the Ctx +func (sto *Store) NewCtx(act Action) *Ctx { + sto.mu.Lock() + defer sto.mu.Unlock() + + return newCtx(sto, nil, act, "", nil) } -func newStore(ag *Agent, l Listener) *Store { +func newStore(ag *Agent, sub Subscriber) *Store { sto := &Store{ - readyCh: make(chan struct{}), - listener: l, - state: newState(ag.Store), - ag: ag, + sub: sub, + ag: ag, } - sto.tasks = newTaskTracker(sto.Dispatch) + sto.state = newState(sto) + sto.tasks = &taskTracker{} sto.After(sto.tasks) + + // 640 slots ought to be enough for anybody + sto.dsp.lo = make(chan dispatchHandler, 640) + sto.dsp.hi = make(chan dispatchHandler, 640) + + sto.mounted = map[Reducer]bool{} + return sto } -func (sto *Store) Subscribe(l Listener) (unsubscribe func()) { +func (sto *Store) Subscribe(sub Subscriber) (unsubscribe func()) { sto.mu.Lock() defer sto.mu.Unlock() - p := &struct{ Listener }{l} - sto.listeners = append(sto.listeners[:len(sto.listeners):len(sto.listeners)], p) + p := &struct{ Subscriber }{sub} + sto.subs = append(sto.subs[:len(sto.subs):len(sto.subs)], p) return func() { sto.mu.Lock() defer sto.mu.Unlock() - listeners := make([]*struct{ Listener }, 0, len(sto.listeners)-1) - for _, q := range sto.listeners { + subs := make([]*struct{ Subscriber }, 0, len(sto.subs)-1) + for _, q := range sto.subs { if p != q { - listeners = append(listeners, q) + subs = append(subs, q) } } - sto.listeners = listeners + sto.subs = subs } } diff --git a/src/margo.sh/mg/tasks.go b/src/margo.sh/mg/tasks.go index a577668b..89abf838 100644 --- a/src/margo.sh/mg/tasks.go +++ b/src/margo.sh/mg/tasks.go @@ -42,18 +42,27 @@ func (ti *TaskTicket) Cancellable() bool { } type taskTracker struct { - mu sync.Mutex - id uint64 - tickets []*TaskTicket - timer *time.Timer - dispatch Dispatcher - buf bytes.Buffer + ReducerType + mu sync.Mutex + id uint64 + tickets []*TaskTicket + timer *time.Timer + buf bytes.Buffer } -func newTaskTracker(dispatch Dispatcher) *taskTracker { - return &taskTracker{ - timer: time.NewTimer(1 * time.Second), - dispatch: dispatch, +func (tr *taskTracker) ReducerMount(mx *Ctx) { + tr.timer = time.NewTimer(1 * time.Second) + dispatch := mx.Store.Dispatch + go func() { + for range tr.timer.C { + dispatch(taskTick{}) + } + }() +} + +func (tr *taskTracker) ReducerUnmount(*Ctx) { + for _, t := range tr.tickets { + t.Cancel() } } @@ -63,22 +72,12 @@ func (tr *taskTracker) Reduce(mx *Ctx) *State { st := mx.State switch mx.Action.(type) { - case Started: - tr.start() - case Shutdown: - for _, t := range tr.tickets { - t.Cancel() - } case RunCmd: - st = st.AddBuiltinCmds(BultinCmd{ - Name: ".kill", - Desc: "List and cancel active tasks", - Run: tr.killBuiltin, - }) + st = tr.runCmd(st) + case QueryUserCmds: + st = tr.userCmds(st) case taskTick: - if len(tr.tickets) != 0 { - tr.resetTimer() - } + tr.tick() } if s := tr.status(); s != "" { st = st.AddStatus(s) @@ -86,6 +85,45 @@ func (tr *taskTracker) Reduce(mx *Ctx) *State { return st } +func (tr *taskTracker) tick() { + if len(tr.tickets) != 0 { + tr.resetTimer() + } +} + +func (tr *taskTracker) userCmds(st *State) *State { + cl := make([]UserCmd, len(tr.tickets)) + for i, t := range tr.tickets { + c := UserCmd{ + Title: "Cancel " + t.Title, + Name: ".kill", + } + for _, s := range []string{t.CancelID, t.ID} { + if s != "" { + c.Args = append(c.Args, s) + } + } + cl[i] = c + } + return st.AddUserCmds(cl...) +} + +func (tr *taskTracker) runCmd(st *State) *State { + return st.AddBuiltinCmds( + BultinCmd{ + Name: ".kill", + Desc: "List and cancel active tasks", + Run: tr.killBuiltin, + }, + ) +} + +func (tr *taskTracker) shutdown(mx *Ctx) { + for _, t := range tr.tickets { + t.Cancel() + } +} + // Cancel cancels the task tid. // true is returned if the task exists and was canceled func (tr *taskTracker) Cancel(tid string) bool { @@ -203,14 +241,6 @@ func (tr *taskTracker) titles() (stale []string, fresh []string) { return stale, fresh } -func (tr *taskTracker) start() { - go func() { - for range tr.timer.C { - tr.dispatch(taskTick{}) - } - }() -} - func (tr *taskTracker) resetTimer() { defer tr.timer.Reset(1 * time.Second) } diff --git a/src/margo.sh/mgutil/chanq.go b/src/margo.sh/mgutil/chanq.go new file mode 100644 index 00000000..11ebc53e --- /dev/null +++ b/src/margo.sh/mgutil/chanq.go @@ -0,0 +1,62 @@ +package mgutil + +import ( + "sync" +) + +// ChanQ is a bounded queue +type ChanQ struct { + c chan interface{} + mu sync.Mutex + closed bool +} + +// Put puts v into the queue. +// It removes the oldest value if no space is available. +func (q *ChanQ) Put(v interface{}) { + q.mu.Lock() + defer q.mu.Unlock() + + if q.closed { + return + } + + for { + select { + case q.c <- v: + return + case _, open := <-q.c: + if !open { + return + } + } + } +} + +// C returns a channel on which values are sent +func (q *ChanQ) C() <-chan interface{} { + return q.c +} + +// Close closes the queue and the channel returned by C(). +// closing a closed queue has no effect. +func (q *ChanQ) Close() { + q.mu.Lock() + defer q.mu.Unlock() + + if q.closed { + return + } + + q.closed = true + close(q.c) +} + +// NewChanQ creates a new ChanQ +// if cap is less than 1, it panics +func NewChanQ(cap int) *ChanQ { + if cap < 1 { + panic("ChanQ cap must be greater than, or equal to, one") + } + return &ChanQ{c: make(chan interface{}, cap)} +} diff --git a/src/margo.sh/mgutil/chanq_test.go b/src/margo.sh/mgutil/chanq_test.go new file mode 100644 index 00000000..9f41f261 --- /dev/null +++ b/src/margo.sh/mgutil/chanq_test.go @@ -0,0 +1,30 @@ +package mgutil + +import ( + "fmt" + "testing" +) + +func TestChanQ(t *testing.T) { + for _, i := range []int{0, -1} { + name := fmt.Sprintf("NewChanQ(%d)", i) + t.Run(name, func(t *testing.T) { + defer func() { + if v := recover(); v == nil { + t.Errorf("%s does not result in a panic", name) + } + }() + NewChanQ(i) + }) + } + + cq := NewChanQ(1) + lastVal := -1 + for i := 0; i < 3; i++ { + lastVal = i + cq.Put(lastVal) + } + if v := <-cq.C(); v != lastVal { + t.Error("CtxQ.Put does not appear to clear the old value") + } +} diff --git a/src/margo.sh/mgutil/iowrapper.go b/src/margo.sh/mgutil/iowrapper.go new file mode 100644 index 00000000..4cdaea73 --- /dev/null +++ b/src/margo.sh/mgutil/iowrapper.go @@ -0,0 +1,65 @@ +package mgutil + +import ( + "io" + "sync" +) + +// IOWrapper implements various optional io interfaces. +// It delegates to the interface fields that are not nil +type IOWrapper struct { + // If Locker is not nil, all methods are called while holding the lock + Locker sync.Locker + + // If Reader is not nil, it will be called to handle reads + Reader io.Reader + + // If Writer is not nil, it will be called to handle writes + Writer io.Writer + + // If Closer is not nil, it will be called to handle closes + Closer io.Closer +} + +// lockUnlock locks Locker if it's not nil and returns Locker.Unlock +// otherwise it returns a nop unlock function +func (iow *IOWrapper) lockUnlock() func() { + if mu := iow.Locker; mu != nil { + mu.Lock() + return mu.Unlock + } + return func() {} +} + +// Read calls Reader.Read() if Reader is not nil +// otherwise it returns `0, io.EOF` +func (iow *IOWrapper) Read(p []byte) (int, error) { + defer iow.lockUnlock()() + + if r := iow.Reader; r != nil { + return r.Read(p) + } + return 0, io.EOF +} + +// Write calls Writer.Write() if Writer is not nil +// otherwise it returns `len(p), nil` +func (iow *IOWrapper) Write(p []byte) (int, error) { + defer iow.lockUnlock()() + + if w := iow.Writer; w != nil { + return w.Write(p) + } + return len(p), nil +} + +// Write calls Closer.Close() if Closer is not nil +// otherwise it returns `nil` +func (iow *IOWrapper) Close() error { + defer iow.lockUnlock()() + + if c := iow.Closer; c != nil { + return c.Close() + } + return nil +} diff --git a/src/margo.sh/mgutil/nocopy.go b/src/margo.sh/mgutil/nocopy.go new file mode 100644 index 00000000..13c06a70 --- /dev/null +++ b/src/margo.sh/mgutil/nocopy.go @@ -0,0 +1,11 @@ +package mgutil + +// NoCopy is used to prevent struct copying through `go vet`s -copylocks checker. +// It's an export of the type `sync.noCopy` +// See https://golang.org/issues/8005#issuecomment-190753527 +// +// To prevent struct copying, add a field `noCopy NoCopy` to the struct +type NoCopy struct{} + +// Lock is a no-op used by the `go vet -copylocks` checker. +func (*NoCopy) Lock() {} diff --git a/src/margo.sh/mgutil/sync.go b/src/margo.sh/mgutil/sync.go new file mode 100644 index 00000000..62a97024 --- /dev/null +++ b/src/margo.sh/mgutil/sync.go @@ -0,0 +1,19 @@ +package mgutil + +import ( + "sync/atomic" +) + +type AtomicBool struct{ n int32 } + +func (a *AtomicBool) Set(v bool) { + if v { + atomic.StoreInt32(&a.n, 1) + } else { + atomic.StoreInt32(&a.n, 0) + } +} + +func (a *AtomicBool) IsSet() bool { + return atomic.LoadInt32(&a.n) != 0 +} diff --git a/src/margo.sh/misc/pf/pf.go b/src/margo.sh/misc/pf/pf.go new file mode 100644 index 00000000..0555f4db --- /dev/null +++ b/src/margo.sh/misc/pf/pf.go @@ -0,0 +1,191 @@ +package pf + +import ( + "fmt" + "io" + "margo.sh/mgutil" + "sync" + "time" +) + +var ( + enabled = &mgutil.AtomicBool{} + + DefaultPrintOpts = PrintOpts{ + Indent: "\t", + } +) + +func Enabled() bool { + return enabled.IsSet() +} + +func Enable() { + enabled.Set(true) +} + +func Disable() { + enabled.Set(false) +} + +type Dur struct { + time.Duration +} + +func (d Dur) String() string { + p := d.Duration + switch { + case p < time.Millisecond: + return p.Round(time.Microsecond).String() + case p < time.Minute: + return p.Round(time.Millisecond).String() + default: + return p.Round(time.Second).String() + } +} + +func D(d time.Duration) Dur { + return Dur{Duration: d} +} + +type PrintOpts struct { + Prefix string + Indent string + MinDuration time.Duration +} + +type Node struct { + Name string + Duration time.Duration + Samples int + Children []*Node + + start time.Time +} + +func (n *Node) Dur() Dur { + return D(n.duration()) +} + +func (n *Node) child(name string) *Node { + for _, c := range n.Children { + if c.Name == name { + return c + } + } + c := &Node{Name: name} + n.Children = append(n.Children, c) + return c +} + +func (n *Node) duration() time.Duration { + d := n.Duration + if d > 0 { + return d + } + + for _, c := range n.Children { + d += c.Duration + } + return d +} + +func (n *Node) fprint(w io.Writer, o PrintOpts) { + subTitle := "" + if i := n.Samples; i >= 2 { + subTitle = fmt.Sprintf("*%d", i) + } + fmt.Fprintf(w, "%s%s%s %s\n", o.Prefix, n.Name, subTitle, n.Dur()) + + o.Prefix += o.Indent + for _, c := range n.Children { + if c.Duration >= o.MinDuration { + c.fprint(w, o) + } + } +} + +type Profile struct { + root *Node + stack []*Node + mu sync.RWMutex +} + +func (p *Profile) Dur() Dur { + p.mu.RLock() + defer p.mu.RUnlock() + + return p.root.Dur() +} + +func (p *Profile) Do(name string, f func()) { + defer p.Push(name).Pop() + f() +} + +func (p *Profile) Push(name string) *Profile { + p.update(func() { + n := p.stack[len(p.stack)-1].child(name) + n.start = time.Now() + p.stack = append(p.stack, n) + }) + return p +} + +func (p *Profile) Pop() { + p.update(func() { + n := p.stack[len(p.stack)-1] + n.Duration += time.Since(n.start) + n.Samples++ + p.stack = p.stack[:len(p.stack)-1] + }) +} + +func (p *Profile) Sample(name string, d time.Duration) { + p.update(func() { + n := p.stack[len(p.stack)-1].child(name) + n.Duration += d + n.Samples++ + }) +} + +func (p *Profile) Fprint(w io.Writer, opts *PrintOpts) { + p.mu.Lock() + defer p.mu.Unlock() + + o := DefaultPrintOpts + if opts != nil { + o = *opts + if o.Indent == "" { + o.Indent = DefaultPrintOpts.Indent + } + } + + p.root.fprint(w, o) +} + +func (p *Profile) SetName(name string) { + p.mu.Lock() + defer p.mu.Unlock() + + p.root.Name = name +} + +func (p *Profile) update(f func()) { + if !Enabled() { + return + } + p.mu.Lock() + defer p.mu.Unlock() + + f() +} + +func NewProfile(name string) *Profile { + n := &Node{Name: name} + return &Profile{root: n, stack: []*Node{n}} +} + +func Since(t time.Time) Dur { + return D(time.Since(t)) +} diff --git a/src/margo.sh/sublime/config.go b/src/margo.sh/sublime/config.go index 5d6f4144..6d805ec6 100644 --- a/src/margo.sh/sublime/config.go +++ b/src/margo.sh/sublime/config.go @@ -5,7 +5,7 @@ import ( ) var ( - DefaultConfig = Config{} + DefaultConfig = Config{}.EnabledForLangs("*") _ mg.EditorConfig = DefaultConfig ) diff --git a/src/margo.sh/sublime/ext.go b/src/margo.sh/sublime/ext.go index 59930b85..1b4ef954 100644 --- a/src/margo.sh/sublime/ext.go +++ b/src/margo.sh/sublime/ext.go @@ -2,7 +2,15 @@ package sublime import ( "margo.sh/mg" + "runtime" ) -func Margo(mo mg.Args) { +func Margo(ma mg.Args) { + ma.Store.Use(mg.NewReducer(func(mx *mg.Ctx) *mg.State { + ctrl := "ctrl" + if runtime.GOOS == "darwin" { + ctrl = "super" + } + return mx.AddStatusf("press ` %s+. `,` %s+x ` to configure margo", ctrl, ctrl) + })) } diff --git a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go index a1e08d7e..4d31dd0f 100644 --- a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go +++ b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go @@ -6,21 +6,12 @@ package blake2b -import _ "unsafe" - -//go:linkname x86_HasAVX internal/cpu.X86.HasAVX -var x86_HasAVX bool - -//go:linkname x86_HasAVX2 internal/cpu.X86.HasAVX2 -var x86_HasAVX2 bool - -//go:linkname x86_HasAVX internal/cpu.X86.HasSSE4 -var x86_HasSSE4 bool +import "golang.org/x/sys/cpu" func init() { - useAVX2 = x86_HasAVX2 - useAVX = x86_HasAVX - useSSE4 = x86_HasSSE4 + useAVX2 = cpu.X86.HasAVX2 + useAVX = cpu.X86.HasAVX + useSSE4 = cpu.X86.HasSSE41 } //go:noescape @@ -33,13 +24,14 @@ func hashBlocksAVX(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { - if useAVX2 { + switch { + case useAVX2: hashBlocksAVX2(h, c, flag, blocks) - } else if useAVX { + case useAVX: hashBlocksAVX(h, c, flag, blocks) - } else if useSSE4 { + case useSSE4: hashBlocksSSE4(h, c, flag, blocks) - } else { + default: hashBlocksGeneric(h, c, flag, blocks) } } diff --git a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s index 5593b1b3..784bce6a 100644 --- a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s +++ b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s @@ -748,3 +748,15 @@ noinc: MOVQ BP, SP RET + +// func supportsAVX2() bool +TEXT ·supportsAVX2(SB), 4, $0-1 + MOVQ runtime·support_avx2(SB), AX + MOVB AX, ret+0(FP) + RET + +// func supportsAVX() bool +TEXT ·supportsAVX(SB), 4, $0-1 + MOVQ runtime·support_avx(SB), AX + MOVB AX, ret+0(FP) + RET diff --git a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go index 2ab7c30f..30e2fcd5 100644 --- a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go +++ b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go @@ -6,13 +6,12 @@ package blake2b +import "golang.org/x/sys/cpu" + func init() { - useSSE4 = supportsSSE4() + useSSE4 = cpu.X86.HasSSE41 } -//go:noescape -func supportsSSE4() bool - //go:noescape func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) diff --git a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s index 64530740..578e947b 100644 --- a/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s +++ b/src/margo.sh/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s @@ -279,12 +279,3 @@ noinc: MOVQ BP, SP RET - -// func supportsSSE4() bool -TEXT ·supportsSSE4(SB), 4, $0-1 - MOVL $1, AX - CPUID - SHRL $19, CX // Bit 19 indicates SSE4 support - ANDL $1, CX // CX != 0 if support SSE4 - MOVB CX, ret+0(FP) - RET diff --git a/src/margo.sh/vendor/golang.org/x/sys/AUTHORS b/src/margo.sh/vendor/golang.org/x/sys/AUTHORS new file mode 100644 index 00000000..15167cd7 --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/src/margo.sh/vendor/golang.org/x/sys/CONTRIBUTORS b/src/margo.sh/vendor/golang.org/x/sys/CONTRIBUTORS new file mode 100644 index 00000000..1c4577e9 --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/src/margo.sh/vendor/golang.org/x/sys/LICENSE b/src/margo.sh/vendor/golang.org/x/sys/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/margo.sh/vendor/golang.org/x/sys/PATENTS b/src/margo.sh/vendor/golang.org/x/sys/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu.go b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu.go new file mode 100644 index 00000000..2d1fc12f --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu.go @@ -0,0 +1,35 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package cpu implements processor feature detection for +// various CPU architectures. +package cpu + +// X86 contains the supported CPU features of the +// current X86/AMD64 platform. If the current platform +// is not X86/AMD64 then all feature flags are false. +// +// X86 is padded to avoid false sharing. Further the HasAVX +// and HasAVX2 are only set if the OS supports XMM and YMM +// registers in addition to the CPUID feature bit being set. +var X86 struct { + _ [cacheLineSize]byte + HasAES bool // AES hardware implementation (AES NI) + HasADX bool // Multi-precision add-carry instruction extensions + HasAVX bool // Advanced vector extension + HasAVX2 bool // Advanced vector extension 2 + HasBMI1 bool // Bit manipulation instruction set 1 + HasBMI2 bool // Bit manipulation instruction set 2 + HasERMS bool // Enhanced REP for MOVSB and STOSB + HasFMA bool // Fused-multiply-add instructions + HasOSXSAVE bool // OS supports XSAVE/XRESTOR for saving/restoring XMM registers. + HasPCLMULQDQ bool // PCLMULQDQ instruction - most often used for AES-GCM + HasPOPCNT bool // Hamming weight instruction POPCNT. + HasSSE2 bool // Streaming SIMD extension 2 (always available on amd64) + HasSSE3 bool // Streaming SIMD extension 3 + HasSSSE3 bool // Supplemental streaming SIMD extension 3 + HasSSE41 bool // Streaming SIMD extension 4 and 4.1 + HasSSE42 bool // Streaming SIMD extension 4 and 4.2 + _ [cacheLineSize]byte +} diff --git a/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_arm.go b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_arm.go new file mode 100644 index 00000000..d93036f7 --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_arm.go @@ -0,0 +1,7 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +const cacheLineSize = 32 diff --git a/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_arm64.go b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_arm64.go new file mode 100644 index 00000000..1d2ab290 --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_arm64.go @@ -0,0 +1,7 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +const cacheLineSize = 64 diff --git a/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_mips64x.go b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_mips64x.go new file mode 100644 index 00000000..6165f121 --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_mips64x.go @@ -0,0 +1,9 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build mips64 mips64le + +package cpu + +const cacheLineSize = 32 diff --git a/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_mipsx.go b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_mipsx.go new file mode 100644 index 00000000..1269eee8 --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_mipsx.go @@ -0,0 +1,9 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build mips mipsle + +package cpu + +const cacheLineSize = 32 diff --git a/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_ppc64x.go b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_ppc64x.go new file mode 100644 index 00000000..d10759a5 --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_ppc64x.go @@ -0,0 +1,9 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ppc64 ppc64le + +package cpu + +const cacheLineSize = 128 diff --git a/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_s390x.go b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_s390x.go new file mode 100644 index 00000000..684c4f00 --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_s390x.go @@ -0,0 +1,7 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +const cacheLineSize = 256 diff --git a/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_x86.go b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_x86.go new file mode 100644 index 00000000..8842b7cf --- /dev/null +++ b/src/margo.sh/vendor/golang.org/x/sys/cpu/cpu_x86.go @@ -0,0 +1,61 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build 386 amd64 amd64p32 + +package cpu + +const cacheLineSize = 64 + +// cpuid is implemented in cpu_x86.s. +func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) + +// xgetbv with ecx = 0 is implemented in cpu_x86.s. +func xgetbv() (eax, edx uint32) + +func init() { + maxID, _, _, _ := cpuid(0, 0) + + if maxID < 1 { + return + } + + _, _, ecx1, edx1 := cpuid(1, 0) + X86.HasSSE2 = isSet(26, edx1) + + X86.HasSSE3 = isSet(0, ecx1) + X86.HasPCLMULQDQ = isSet(1, ecx1) + X86.HasSSSE3 = isSet(9, ecx1) + X86.HasFMA = isSet(12, ecx1) + X86.HasSSE41 = isSet(19, ecx1) + X86.HasSSE42 = isSet(20, ecx1) + X86.HasPOPCNT = isSet(23, ecx1) + X86.HasAES = isSet(25, ecx1) + X86.HasOSXSAVE = isSet(27, ecx1) + + osSupportsAVX := false + // For XGETBV, OSXSAVE bit is required and sufficient. + if X86.HasOSXSAVE { + eax, _ := xgetbv() + // Check if XMM and YMM registers have OS support. + osSupportsAVX = isSet(1, eax) && isSet(2, eax) + } + + X86.HasAVX = isSet(28, ecx1) && osSupportsAVX + + if maxID < 7 { + return + } + + _, ebx7, _, _ := cpuid(7, 0) + X86.HasBMI1 = isSet(3, ebx7) + X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX + X86.HasBMI2 = isSet(8, ebx7) + X86.HasERMS = isSet(9, ebx7) + X86.HasADX = isSet(19, ebx7) +} + +func isSet(bitpos uint, value uint32) bool { + return value&(1< Date: Wed, 2 May 2018 18:36:02 +0100 Subject: [PATCH 022/186] misc tweaks * fix a ui freeze when a response comes back while we're executing a blocking request * add Sent time to the requests to margo profiling can track transport time * separate the status messages with spaces to make it a little easier to read * make enabled_for_langs '*' by default * when pressing ctrl+.,ctrl+x if the view is already open, don't change the scroll position --- gosubl/_dbg.py | 2 +- gosubl/gs.py | 5 +++++ gosubl/margo.py | 22 ++++++++++++++-------- gosubl/margo_agent.py | 24 ++++++++++++++++-------- gosubl/margo_render.py | 9 +++------ gosubl/margo_state.py | 6 ++++++ gosubl/margo_sublime.py | 4 ++-- gosubl/mg9.py | 1 + gs9o.py | 4 ++-- 9 files changed, 50 insertions(+), 27 deletions(-) diff --git a/gosubl/_dbg.py b/gosubl/_dbg.py index 3ce52be7..8b3c40b1 100644 --- a/gosubl/_dbg.py +++ b/gosubl/_dbg.py @@ -2,7 +2,7 @@ import time class pf(object): - def __init__(self, print='{name}: {dur}', name='', dot='', gt=0.020): + def __init__(self, *, print='{name}: {dur}', name='', dot='', gt=0.020): self.start = time.time() self.end = 0 self.duration = 0 diff --git a/gosubl/gs.py b/gosubl/gs.py index 6a804057..c8b832bd 100644 --- a/gosubl/gs.py +++ b/gosubl/gs.py @@ -543,7 +543,12 @@ def do_focus(fn, row, col, win, focus_pat, cb): r = view.find(focus_pat, 0) if r: row, col = view.rowcol(r.begin()) + + if row < 0: + row, col = rowcol(view) + view.run_command("gs_goto_row_col", { "row": row, "col": col }) + if cb: cb(True) diff --git a/gosubl/margo.py b/gosubl/margo.py index e1857dab..5a882857 100644 --- a/gosubl/margo.py +++ b/gosubl/margo.py @@ -17,12 +17,15 @@ def __init__(self): self.agent_tokens = TokenCounter('agent', format='{}#{:03d}', start=6) self.run_tokens = TokenCounter('9o.run') self.agent = None - self.enabled_for_langs = [] + self.enabled_for_langs = ['*'] self.state = State() self.status = [] self.output_handler = None def render(self, rs=None): + # ST has some locking issues due to its "thread-safe" API + # don't access things like sublime.active_view() directly + if rs: self.state = rs.state cfg = rs.state.config @@ -32,14 +35,17 @@ def render(self, rs=None): if cfg.override_settings: gs._mg_override_settings = cfg.override_settings - render(view=gs.active_view(), state=self.state, status=self.status) + def _render(): + render(view=gs.active_view(), state=self.state, status=self.status) - if rs: - if rs.agent is self.agent: - sublime.set_timeout_async(lambda: self._handle_client_actions(rs), 0) + if rs: + self._handle_client_actions(rs) + + if rs.agent and rs.agent is not self.agent: + rs.agent.stop() + + sublime.set_timeout(_render) - if rs.agent and rs.agent is not self.agent: - rs.agent.stop() def _handle_act_restart(self, rs, act): self.restart() @@ -159,7 +165,7 @@ def on_query_completions(self, view, prefix, locations): if not lang: return None - rs = self.send(view=view, actions=[actions.QueryCompletions]).wait(0.300) + rs = self.send(view=view, actions=[actions.QueryCompletions]).wait(0.500) if not rs: self.out.println('aborting QueryCompletions. it did not respond in time') return None diff --git a/gosubl/margo_agent.py b/gosubl/margo_agent.py index cc92b378..76d8c37d 100644 --- a/gosubl/margo_agent.py +++ b/gosubl/margo_agent.py @@ -2,6 +2,7 @@ from . import sh, gs, gsq from .margo_common import TokenCounter, OutputLogger, Chan from .margo_state import State, make_props, actions +from datetime import datetime import os import sublime import subprocess @@ -9,17 +10,24 @@ import time ipc_codec = 'msgpack' - +ipc_silent_exceptions = ( + EOFError, + BrokenPipeError, + ValueError, +) if ipc_codec == 'msgpack': from .vendor import umsgpack + ipc_loads = umsgpack.loads ipc_dec = umsgpack.load ipc_enc = umsgpack.dump - ipc_ignore_exceptions = (umsgpack.InsufficientDataException, BrokenPipeError, ValueError) + ipc_silent_exceptions += ( + umsgpack.InsufficientDataException, + ) elif ipc_codec == 'cbor': from .vendor.cbor_py import cbor + ipc_loads = cbor.loads ipc_dec = cbor.load ipc_enc = cbor.dump - ipc_ignore_exceptions = (BrokenPipeError, ValueError) else: raise Exception('impossibru') @@ -155,7 +163,7 @@ def _handle_send_ipc(self, rq): try: ipc_enc(rq.data(), self.proc.stdin) exc = None - except ipc_ignore_exceptions as e: + except ipc_silent_exceptions as e: exc = e except Exception as e: exc = e @@ -195,10 +203,9 @@ def queue(self, *, actions=[], view=None): def send(self, *, actions=[], cb=None, view=None): view = gs.active_view(view=view) - acts = self._queued_acts(view) if not isinstance(actions, list): - raise Exception('actions is %s' % type(actions)) - acts.extend(actions) + raise Exception('actions must be a list, not %s' % type(actions)) + acts = self._queued_acts(view) + actions rq = AgentReq(self, acts, cb=cb, view=view) timeout = 0.200 if not self.started.wait(timeout): @@ -267,7 +274,7 @@ def _handle_recv(self): v = ipc_dec(self.proc.stdout) or {} if v: self._handle_recv_ipc(v) - except ipc_ignore_exceptions: + except ipc_silent_exceptions: pass except Exception as e: self.out.println('ipc: recv: %s: %s' % (e, v)) @@ -352,6 +359,7 @@ def data(self): 'Cookie': self.cookie, 'Props': self.props, 'Actions': self.actions, + 'Sent': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f'), } DEFAULT_RESPONSE = AgentRes(error='default agent response') diff --git a/gosubl/margo_render.py b/gosubl/margo_render.py index ab966d4a..3363c87d 100644 --- a/gosubl/margo_render.py +++ b/gosubl/margo_render.py @@ -5,14 +5,11 @@ import sublime STATUS_KEY = '#mg.Status' -STATUS_PFX = '• ' -STATUS_SFX = ' •' -STATUS_SEP = ' •• ' +STATUS_PFX = ' ' +STATUS_SFX = ' ' +STATUS_SEP = ' ' def render(view, state, status=[]): - sublime.set_timeout_async(lambda: _render(view, state, status), 0) - -def _render(view, state, status): _render_status(view, status + state.status) _render_issues(view, state.issues) diff --git a/gosubl/margo_state.py b/gosubl/margo_state.py index eba4a751..7bc75fe2 100644 --- a/gosubl/margo_state.py +++ b/gosubl/margo_state.py @@ -148,6 +148,12 @@ def relpath(self, dir): return os.path.relpath(self.path, dir) + def basename(self): + if not self.path: + return self.name + + return os.path.basename(self.path) + class ResView(object): def __init__(self, v={}): self.name = v.get('Name') or '' diff --git a/gosubl/margo_sublime.py b/gosubl/margo_sublime.py index 78e1a2c0..f4a4d97d 100644 --- a/gosubl/margo_sublime.py +++ b/gosubl/margo_sublime.py @@ -108,7 +108,7 @@ def sort_key(isu): selected = [] for idx, isu in enumerate(index): if vp.match(isu): - title = 'Line %d' % (isu.row + 1) + title = '%s:%d' % (isu.basename(), isu.row + 1) selected.append((abs(isu.row - row), idx)) else: title = '%s:%d' % (isu.relpath(dir) or isu.name, isu.row + 1) @@ -170,5 +170,5 @@ class MargoOpenExtensionCommand(sublime_plugin.WindowCommand): def run(self): fn = mg.extension_file(True) if fn: - gs.focus(fn, focus_pat='func Margo') + gs.focus(fn, row=-1, focus_pat='') diff --git a/gosubl/mg9.py b/gosubl/mg9.py index a5d6b510..d11a140e 100644 --- a/gosubl/mg9.py +++ b/gosubl/mg9.py @@ -483,6 +483,7 @@ def share(src, f): f({}, 'Share cancelled') def acall(method, arg, cb): + print('acall', method) gs.mg9_send_q.put((method, arg, cb)) def bcall(method, arg, err_title=''): diff --git a/gs9o.py b/gs9o.py index f3d795fe..3bc32c3f 100644 --- a/gs9o.py +++ b/gs9o.py @@ -565,7 +565,7 @@ def _save_all(win, wd): try: fn = v.file_name() if fn and v.is_dirty() and fn.endswith('.go') and os.path.dirname(fn) == wd: - v.run_command('gs_fmt_save') + v.run_command('save') except Exception: gs.error_traceback(DOMAIN) @@ -695,7 +695,7 @@ def cmd_clear(view, edit, args, wd, rkey): def cmd_go(view, edit, args, wd, rkey): _save_all(view.window(), wd) - _rcmd(view, edit, 'go', args, wd, rkey) + sublime.set_timeout_async(lambda: _rcmd(view, edit, 'go', args, wd, rkey)) def cmd_cancel_replay(view, edit, args, wd, rkey): cid = '' From 49ec7821133c783ab663b71a39ef41fa844a01f9 Mon Sep 17 00:00:00 2001 From: DisposaBoy Date: Tue, 8 May 2018 17:41:49 +0100 Subject: [PATCH 023/186] sync margo --- src/margo.sh/Gopkg.lock | 28 +- src/margo.sh/Gopkg.toml | 14 +- src/margo.sh/cmdpkg/margosublime/main.go | 2 +- .../extension-example/extension-example.go | 8 +- src/margo.sh/format/format.go | 22 +- src/margo.sh/golang/common.go | 10 +- src/margo.sh/golang/gocmd.go | 6 +- src/margo.sh/golang/gocode.go | 16 +- src/margo.sh/golang/gocode_calltips.go | 2 +- src/margo.sh/golang/gofmt.go | 14 +- src/margo.sh/golang/guru.go | 133 + .../golang/internal/gocode/bridge._margo_.go | 4 - src/margo.sh/golang/lint.go | 169 +- src/margo.sh/golang/snippets.go | 8 +- src/margo.sh/golang/syntaxcheck.go | 42 +- src/margo.sh/js/jsonfmt.go | 13 +- src/margo.sh/mg/action.go | 29 +- src/margo.sh/mg/agent.go | 6 +- src/margo.sh/mg/builtins.go | 2 +- src/margo.sh/mg/client-actions.go | 21 +- src/margo.sh/mg/cmd.go | 15 +- src/margo.sh/mg/cmd_internal_test.go | 2 +- src/margo.sh/mg/common.go | 10 +- src/margo.sh/mg/ctx.go | 165 + src/margo.sh/mg/issue.go | 116 +- src/margo.sh/mg/issue_test.go | 4 +- src/margo.sh/mg/langs.go | 50 + src/margo.sh/mg/lint.go | 129 + src/margo.sh/mg/reducers.go | 203 +- src/margo.sh/mg/restart.go | 133 + src/margo.sh/mg/state.go | 162 +- src/margo.sh/mg/store.go | 49 +- src/margo.sh/mg/tasks.go | 19 +- src/margo.sh/mg/view.go | 26 +- src/margo.sh/{misc/pf/pf.go => mgpf/mgpf.go} | 2 +- .../pprof/pprofdo/pprofdo-fallback.go | 0 .../{misc => mgpf}/pprof/pprofdo/pprofdo.go | 0 .../pprof/pprofhttp/pprofhttp.go | 0 src/margo.sh/sublime/config.go | 4 +- .../x/crypto/blake2b/blake2bAVX2_amd64.s | 12 - .../vendor/golang.org/x/tools/AUTHORS | 3 + .../vendor/golang.org/x/tools/CONTRIBUTORS | 3 + .../vendor/golang.org/x/tools/LICENSE | 27 + .../vendor/golang.org/x/tools/PATENTS | 22 + .../golang.org/x/tools/cmd/getgo/LICENSE | 27 + .../golang.org/x/tools/cmd/guru/callees.go | 257 + .../golang.org/x/tools/cmd/guru/callers.go | 195 + .../golang.org/x/tools/cmd/guru/callstack.go | 141 + .../golang.org/x/tools/cmd/guru/definition.go | 205 + .../golang.org/x/tools/cmd/guru/describe.go | 899 ++ .../golang.org/x/tools/cmd/guru/freevars.go | 223 + .../golang.org/x/tools/cmd/guru/guru.go | 401 + .../golang.org/x/tools/cmd/guru/implements.go | 364 + .../golang.org/x/tools/cmd/guru/isAlias18.go | 15 + .../golang.org/x/tools/cmd/guru/isAlias19.go | 15 + .../golang.org/x/tools/cmd/guru/main.go | 215 + .../golang.org/x/tools/cmd/guru/peers.go | 252 + .../golang.org/x/tools/cmd/guru/pointsto.go | 290 + .../vendor/golang.org/x/tools/cmd/guru/pos.go | 142 + .../golang.org/x/tools/cmd/guru/referrers.go | 802 ++ .../x/tools/cmd/guru/serial/serial.go | 259 + .../golang.org/x/tools/cmd/guru/what.go | 282 + .../golang.org/x/tools/cmd/guru/whicherrs.go | 327 + .../x/tools/container/intsets/popcnt_amd64.go | 20 + .../x/tools/container/intsets/popcnt_amd64.s | 30 + .../x/tools/container/intsets/popcnt_gccgo.go | 9 + .../tools/container/intsets/popcnt_gccgo_c.c | 19 + .../tools/container/intsets/popcnt_generic.go | 33 + .../x/tools/container/intsets/sparse.go | 1091 ++ .../x/tools/container/intsets/util.go | 84 + .../x/tools/go/ast/astutil/enclosing.go | 627 ++ .../x/tools/go/ast/astutil/imports.go | 470 + .../x/tools/go/ast/astutil/rewrite.go | 477 + .../golang.org/x/tools/go/ast/astutil/util.go | 14 + .../x/tools/go/buildutil/allpackages.go | 198 + .../x/tools/go/buildutil/fakecontext.go | 108 + .../x/tools/go/buildutil/overlay.go | 103 + .../golang.org/x/tools/go/buildutil/tags.go | 75 + .../golang.org/x/tools/go/buildutil/util.go | 212 + .../x/tools/go/callgraph/callgraph.go | 129 + .../x/tools/go/callgraph/static/static.go | 35 + .../golang.org/x/tools/go/callgraph/util.go | 181 + .../golang.org/x/tools/go/loader/cgo.go | 207 + .../x/tools/go/loader/cgo_pkgconfig.go | 39 + .../golang.org/x/tools/go/loader/doc.go | 205 + .../golang.org/x/tools/go/loader/loader.go | 1077 ++ .../golang.org/x/tools/go/loader/util.go | 124 + .../vendor/golang.org/x/tools/go/pointer/TODO | 33 + .../golang.org/x/tools/go/pointer/analysis.go | 452 + .../golang.org/x/tools/go/pointer/api.go | 285 + .../x/tools/go/pointer/callgraph.go | 61 + .../x/tools/go/pointer/constraint.go | 149 + .../golang.org/x/tools/go/pointer/doc.go | 610 ++ .../golang.org/x/tools/go/pointer/gen.go | 1325 +++ .../golang.org/x/tools/go/pointer/hvn.go | 973 ++ .../x/tools/go/pointer/intrinsics.go | 361 + .../golang.org/x/tools/go/pointer/labels.go | 152 + .../golang.org/x/tools/go/pointer/opt.go | 132 + .../golang.org/x/tools/go/pointer/print.go | 43 + .../golang.org/x/tools/go/pointer/query.go | 221 + .../golang.org/x/tools/go/pointer/reflect.go | 1975 ++++ .../golang.org/x/tools/go/pointer/solve.go | 370 + .../golang.org/x/tools/go/pointer/util.go | 313 + .../golang.org/x/tools/go/ssa/blockopt.go | 187 + .../golang.org/x/tools/go/ssa/builder.go | 2379 ++++ .../vendor/golang.org/x/tools/go/ssa/const.go | 169 + .../golang.org/x/tools/go/ssa/create.go | 263 + .../vendor/golang.org/x/tools/go/ssa/doc.go | 123 + .../vendor/golang.org/x/tools/go/ssa/dom.go | 341 + .../vendor/golang.org/x/tools/go/ssa/emit.go | 468 + .../vendor/golang.org/x/tools/go/ssa/func.go | 689 ++ .../golang.org/x/tools/go/ssa/identical.go | 7 + .../golang.org/x/tools/go/ssa/identical_17.go | 7 + .../vendor/golang.org/x/tools/go/ssa/lift.go | 653 ++ .../golang.org/x/tools/go/ssa/lvalue.go | 120 + .../golang.org/x/tools/go/ssa/methods.go | 239 + .../vendor/golang.org/x/tools/go/ssa/mode.go | 100 + .../vendor/golang.org/x/tools/go/ssa/print.go | 431 + .../golang.org/x/tools/go/ssa/sanity.go | 521 + .../golang.org/x/tools/go/ssa/source.go | 293 + .../vendor/golang.org/x/tools/go/ssa/ssa.go | 1696 +++ .../golang.org/x/tools/go/ssa/ssautil/load.go | 95 + .../x/tools/go/ssa/ssautil/switch.go | 234 + .../x/tools/go/ssa/ssautil/visit.go | 79 + .../golang.org/x/tools/go/ssa/testmain.go | 267 + .../vendor/golang.org/x/tools/go/ssa/util.go | 119 + .../golang.org/x/tools/go/ssa/wrappers.go | 294 + .../x/tools/go/types/typeutil/imports.go | 31 + .../x/tools/go/types/typeutil/map.go | 313 + .../tools/go/types/typeutil/methodsetcache.go | 72 + .../x/tools/go/types/typeutil/ui.go | 52 + .../vendor/golang.org/x/tools/imports/fix.go | 1125 ++ .../golang.org/x/tools/imports/imports.go | 310 + .../golang.org/x/tools/imports/mkindex.go | 173 + .../golang.org/x/tools/imports/mkstdlib.go | 107 + .../golang.org/x/tools/imports/sortimports.go | 212 + .../golang.org/x/tools/imports/zstdlib.go | 9734 +++++++++++++++++ .../x/tools/internal/fastwalk/fastwalk.go | 191 + .../fastwalk/fastwalk_dirent_fileno.go | 13 + .../internal/fastwalk/fastwalk_dirent_ino.go | 14 + .../internal/fastwalk/fastwalk_portable.go | 29 + .../tools/internal/fastwalk/fastwalk_unix.go | 123 + .../x/tools/refactor/importgraph/graph.go | 167 + .../x/tools/third_party/moduleloader/LICENSE | 22 + .../x/tools/third_party/typescript/LICENSE | 55 + .../x/tools/third_party/webcomponents/LICENSE | 27 + 146 files changed, 42034 insertions(+), 548 deletions(-) create mode 100644 src/margo.sh/golang/guru.go create mode 100644 src/margo.sh/mg/ctx.go create mode 100644 src/margo.sh/mg/langs.go create mode 100644 src/margo.sh/mg/lint.go create mode 100644 src/margo.sh/mg/restart.go rename src/margo.sh/{misc/pf/pf.go => mgpf/mgpf.go} (99%) rename src/margo.sh/{misc => mgpf}/pprof/pprofdo/pprofdo-fallback.go (100%) rename src/margo.sh/{misc => mgpf}/pprof/pprofdo/pprofdo.go (100%) rename src/margo.sh/{misc => mgpf}/pprof/pprofhttp/pprofhttp.go (100%) create mode 100644 src/margo.sh/vendor/golang.org/x/tools/AUTHORS create mode 100644 src/margo.sh/vendor/golang.org/x/tools/CONTRIBUTORS create mode 100644 src/margo.sh/vendor/golang.org/x/tools/LICENSE create mode 100644 src/margo.sh/vendor/golang.org/x/tools/PATENTS create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/getgo/LICENSE create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/callees.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/callers.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/callstack.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/definition.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/describe.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/freevars.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/guru.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/implements.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/isAlias18.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/isAlias19.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/main.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/peers.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/pointsto.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/pos.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/referrers.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/serial/serial.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/what.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/cmd/guru/whicherrs.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/container/intsets/popcnt_amd64.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/container/intsets/popcnt_amd64.s create mode 100644 src/margo.sh/vendor/golang.org/x/tools/container/intsets/popcnt_gccgo.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/container/intsets/popcnt_gccgo_c.c create mode 100644 src/margo.sh/vendor/golang.org/x/tools/container/intsets/popcnt_generic.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/container/intsets/sparse.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/container/intsets/util.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ast/astutil/enclosing.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ast/astutil/imports.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ast/astutil/rewrite.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ast/astutil/util.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/buildutil/allpackages.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/buildutil/fakecontext.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/buildutil/overlay.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/buildutil/tags.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/buildutil/util.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/callgraph/callgraph.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/callgraph/static/static.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/callgraph/util.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/loader/cgo.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/loader/cgo_pkgconfig.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/loader/doc.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/loader/loader.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/loader/util.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/TODO create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/analysis.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/api.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/callgraph.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/constraint.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/doc.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/gen.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/hvn.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/intrinsics.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/labels.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/opt.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/print.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/query.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/reflect.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/solve.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/pointer/util.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/blockopt.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/builder.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/const.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/create.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/doc.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/dom.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/emit.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/func.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/identical.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/identical_17.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/lift.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/lvalue.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/methods.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/mode.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/print.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/sanity.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/source.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/ssa.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/ssautil/load.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/ssautil/switch.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/ssautil/visit.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/testmain.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/util.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/ssa/wrappers.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/types/typeutil/imports.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/types/typeutil/map.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/go/types/typeutil/ui.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/imports/fix.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/imports/imports.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/imports/mkindex.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/imports/mkstdlib.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/imports/sortimports.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/imports/zstdlib.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_fileno.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/refactor/importgraph/graph.go create mode 100644 src/margo.sh/vendor/golang.org/x/tools/third_party/moduleloader/LICENSE create mode 100644 src/margo.sh/vendor/golang.org/x/tools/third_party/typescript/LICENSE create mode 100644 src/margo.sh/vendor/golang.org/x/tools/third_party/webcomponents/LICENSE diff --git a/src/margo.sh/Gopkg.lock b/src/margo.sh/Gopkg.lock index de10f658..8c58e411 100644 --- a/src/margo.sh/Gopkg.lock +++ b/src/margo.sh/Gopkg.lock @@ -17,17 +17,39 @@ branch = "master" name = "golang.org/x/crypto" packages = ["blake2b"] - revision = "2c241ca3045ddc354463c376a9515d9f1f1390a4" + revision = "4ec37c66abab2c7e02ae775328b2ff001c3f025a" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["cpu"] - revision = "78d5f264b493f125018180c204871ecf58a2dce1" + revision = "7db1c3b1a98089d0071c84f646ff5c96aad43682" + +[[projects]] + branch = "master" + name = "golang.org/x/tools" + packages = [ + "cmd/guru", + "cmd/guru/serial", + "container/intsets", + "go/ast/astutil", + "go/buildutil", + "go/callgraph", + "go/callgraph/static", + "go/loader", + "go/pointer", + "go/ssa", + "go/ssa/ssautil", + "go/types/typeutil", + "imports", + "internal/fastwalk", + "refactor/importgraph" + ] + revision = "87723262609ca8fd55d449c027454c29cadefd68" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a7d8a00d2d704fb62eed79ee67536c295268edae7c67b316af3af70397acad95" + inputs-digest = "d929fab6918d1833fd0bf96d285bc438bfa936590205bbba2e182511b2bf6ac5" solver-name = "gps-cdcl" solver-version = 1 diff --git a/src/margo.sh/Gopkg.toml b/src/margo.sh/Gopkg.toml index 48f5c2eb..9d2cfa57 100644 --- a/src/margo.sh/Gopkg.toml +++ b/src/margo.sh/Gopkg.toml @@ -1,3 +1,11 @@ +required = [ + "golang.org/x/tools/cmd/guru" +] + +[prune] + go-tests = true + unused-packages = true + [[constraint]] name = "github.com/ugorji/go" branch = "master" @@ -10,6 +18,6 @@ branch = "master" name = "golang.org/x/crypto" -[prune] - go-tests = true - unused-packages = true +[[constraint]] + branch = "master" + name = "golang.org/x/tools" diff --git a/src/margo.sh/cmdpkg/margosublime/main.go b/src/margo.sh/cmdpkg/margosublime/main.go index 90644935..9041a7c2 100644 --- a/src/margo.sh/cmdpkg/margosublime/main.go +++ b/src/margo.sh/cmdpkg/margosublime/main.go @@ -33,7 +33,7 @@ func Main() { return mgcli.Error("agent creation failed:", err) } - ag.Store.EditorConfig(sublime.DefaultConfig) + ag.Store.SetBaseConfig(sublime.DefaultConfig) if margoExt != nil { margoExt(ag.Args()) } diff --git a/src/margo.sh/extension-example/extension-example.go b/src/margo.sh/extension-example/extension-example.go index 860aaba4..9d89e3b0 100644 --- a/src/margo.sh/extension-example/extension-example.go +++ b/src/margo.sh/extension-example/extension-example.go @@ -12,12 +12,12 @@ func Margo(ma mg.Args) { // they are run in the specified order // and should ideally not block for more than a couple milliseconds ma.Store.Use( - // by default, events (e.g. ViewSaved) are triggered in all files - // uncomment the reducer below to restict event to Go(-lang) files - // please note, however, that this mode is not tested + // By default, events (e.g. ViewSaved) are triggered in all files. + // Uncomment the reducer below to restict events to Go(-lang) files. + // Please note, however, that this mode is not tested // and saving a non-go file will not trigger linters, etc. for that go pkg // - // mg.Reduce(func(mx *mg.Ctx) *mg.State { + // mg.NewReducer(func(mx *mg.Ctx) *mg.State { // return mx.SetConfig(mx.Config.EnabledForLangs("go")) // }), diff --git a/src/margo.sh/format/format.go b/src/margo.sh/format/format.go index b2bd1226..2520cec2 100644 --- a/src/margo.sh/format/format.go +++ b/src/margo.sh/format/format.go @@ -19,22 +19,20 @@ type FmtFunc struct { Fmt func(mx *mg.Ctx, src []byte) ([]byte, error) // Langs is the list of languages in which the reducer should run - Langs []string + Langs []mg.Lang - // Actions is a list of additional actions on which the reducer is allowed to run. + // Actions is a list of actions on which the reducer is allowed to run. // The reducer always runs on the ViewFmt action, even if this list is empty. Actions []mg.Action } +// ReducerCond returns true if Langs and Actions matches the Ctx +func (ff FmtFunc) ReducerCond(mx *mg.Ctx) bool { + return mx.ActionIs(ff.Actions...) && mx.LangIs(ff.Langs...) +} + // Reduce implements the FmtFunc reducer. func (ff FmtFunc) Reduce(mx *mg.Ctx) *mg.State { - if !mx.LangIs(ff.Langs...) { - return mx.State - } - if !mx.ActionIs(mg.ViewFmt{}) && !mx.ActionIs(ff.Actions...) { - return mx.State - } - fn := mx.View.Filename() src, err := mx.View.ReadAll() if err != nil { @@ -48,7 +46,7 @@ func (ff FmtFunc) Reduce(mx *mg.Ctx) *mg.State { if err != nil { return mx.AddErrorf("failed to fmt %s: %s\n", fn, err) } - return mx.SetSrc(src) + return mx.SetViewSrc(src) } // FmtCmd is wrapper around FmtFunc for generic fmt commands. @@ -66,9 +64,9 @@ type FmtCmd struct { Env mg.EnvMap // Langs is the list of languages in which the reducer should run - Langs []string + Langs []mg.Lang - // Actions is a list of additional actions on which the reducer is allowed to run. + // Actions is a list of actions on which the reducer is allowed to run. // The reducer always runs on the ViewFmt action, even if this list is empty. Actions []mg.Action } diff --git a/src/margo.sh/golang/common.go b/src/margo.sh/golang/common.go index bff089b6..e2925a9c 100644 --- a/src/margo.sh/golang/common.go +++ b/src/margo.sh/golang/common.go @@ -1,10 +1,10 @@ package golang import ( - "margo.sh/mg" "go/ast" "go/build" "go/token" + "margo.sh/mg" "os" "path/filepath" "reflect" @@ -14,12 +14,12 @@ import ( "unicode/utf8" ) -var ( - CommonPatterns = append([]*regexp.Regexp{ +func init() { + mg.AddCommonPatterns(mg.Go, regexp.MustCompile(`^\s*(?P.+?\.\w+):(?P\d+:)(?P\d+:?)?(?:(?Pwarning|error)[:])?(?P.+?)(?: [(](?P