diff --git a/cmd/connect.yaml b/cmd/connect.yaml new file mode 100644 index 000000000..9958fa4a8 --- /dev/null +++ b/cmd/connect.yaml @@ -0,0 +1,7 @@ +input: + generate: + interval: 1s + mapping: root = "hello world" + +output: + drop: {} diff --git a/go.mod b/go.mod index bad73a915..1ee082b42 100644 --- a/go.mod +++ b/go.mod @@ -10,10 +10,11 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.18.0 github.com/fsnotify/fsnotify v1.9.0 + github.com/goccy/go-yaml v1.11.3 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/gorilla/handlers v1.5.2 github.com/gorilla/mux v1.8.1 - github.com/gorilla/websocket v1.5.3 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/hashicorp/golang-lru/arc/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/influxdata/go-syslog/v3 v3.0.0 @@ -34,6 +35,7 @@ require ( github.com/sirupsen/logrus v1.9.4 github.com/stretchr/testify v1.11.1 github.com/tilinna/z85 v1.0.0 + github.com/tliron/glsp v0.2.2 github.com/urfave/cli/v2 v2.27.7 github.com/xeipuuv/gojsonschema v1.2.0 github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a @@ -51,27 +53,41 @@ require ( require github.com/cespare/xxhash/v2 v2.3.0 // indirect require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gofrs/uuid/v5 v5.4.0 github.com/golang/snappy v0.0.4 // indirect github.com/govalues/decimal v0.1.36 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect github.com/lib/pq v1.10.9 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rickb777/plural v1.4.7 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/sourcegraph/jsonrpc2 v0.2.0 // indirect + github.com/tliron/commonlog v0.2.21 + github.com/tliron/go-kutil v0.4.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect + golang.org/x/net v0.48.0 // indirect golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 9a9c0d4c0..65e6eaef9 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/Jeffail/shutdown v1.1.0 h1:5Bm3llKt0hnRjmTUlxgBnFg/snFfwqTOUMp3So8jCL github.com/Jeffail/shutdown v1.1.0/go.mod h1:5dT4Y1oe60SJELCkmAB1pr9uQyHBhh6cwDLQTfmuO5U= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -19,8 +21,9 @@ github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065na github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I= @@ -38,6 +41,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= +github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -54,14 +59,17 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/govalues/decimal v0.1.36 h1:dojDpsSvrk0ndAx8+saW5h9WDIHdWpIwrH/yhl9olyU= github.com/govalues/decimal v0.1.36/go.mod h1:Ee7eI3Llf7hfqDZtpj8Q6NCIgJy1iY3kH1pSwDrNqlM= github.com/hashicorp/golang-lru/arc/v2 v2.0.7 h1:QxkVTxwColcduO+LP7eJO56r2hFiG8zEbfAAzRv52KQ= github.com/hashicorp/golang-lru/arc/v2 v2.0.7/go.mod h1:Pe7gBlGdc8clY5LJ0LpJXMt5AmgmWNH1g+oFFVUHOEc= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/influxdata/go-syslog/v3 v3.0.0 h1:jichmjSZlYK0VMmlz+k4WeOQd7z745YLsvGMqwtYt4I= github.com/influxdata/go-syslog/v3 v3.0.0/go.mod h1:tulsOp+CecTAYC27u9miMgq21GqXRW6VdKbOG+QSP4Q= github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc= @@ -85,6 +93,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linkedin/goavro/v2 v2.15.0 h1:pDj1UrjUOO62iXhgBiE7jQkpNIc5/tA5eZsgolMjgVI= github.com/linkedin/goavro/v2 v2.15.0/go.mod h1:KXx+erlq+RPlGSPmLF7xGo6SAbh8sCQ53x064+ioxhk= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE= github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -94,6 +104,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 h1:NHrXEjTNQY7P0Zfx1aMrNhpgxHmow66XQtm0aQLY0AE= github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -102,10 +114,15 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE= +github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0= github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/protocolbuffers/txtpbfmt v0.0.0-20251016062345-16587c79cd91 h1:s1LvMaU6mVwoFtbxv/rCZKE7/fwDmDY684FfUe4c1Io= github.com/protocolbuffers/txtpbfmt v0.0.0-20251016062345-16587c79cd91/go.mod h1:JSbkp0BviKovYYt9XunS95M3mLPibE9bGg+Y95DsEEY= github.com/quipo/dependencysolver v0.0.0-20170801134659-2b009cb4ddcc h1:hK577yxEJ2f5s8w2iy2KimZmgrdAUZUNftE1ESmg2/Q= @@ -118,16 +135,22 @@ github.com/rickb777/period v1.0.22 h1:/X41JreTYsjifLDGBCaVN+s5r5/G0sTbKoHBaISv2n github.com/rickb777/period v1.0.22/go.mod h1:liTmui1MSVgOqkJemF3K6c35CqiEHp0oGHCNZIXnIMA= github.com/rickb777/plural v1.4.7 h1:rBRAxp9aTFYzWTLWIE/UTwKcaqSSAV2ml7aOUFYpAGo= github.com/rickb777/plural v1.4.7/go.mod h1:DB19dtrplGS5s6VJVHn7tvmFYPoE83p1xqio3oVnNRM= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= +github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sourcegraph/jsonrpc2 v0.2.0 h1:KjN/dC4fP6aN9030MZCJs9WQbTOjWHhrtKVpzzSrr/U= +github.com/sourcegraph/jsonrpc2 v0.2.0/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -138,6 +161,12 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tilinna/z85 v1.0.0 h1:uqFnJBlD01dosSeo5sK1G1YGbPuwqVHqR+12OJDRjUw= github.com/tilinna/z85 v1.0.0/go.mod h1:EfpFU/DUY4ddEy6CRvk2l+UQNEzHbh+bqBQS+04Nkxs= +github.com/tliron/commonlog v0.2.21 h1:V1v+6opmzuOqDxxnxxM5RWtlHZmqZlDxkKeZGs6DpPg= +github.com/tliron/commonlog v0.2.21/go.mod h1:W6XVoS/zo7mHXv2Kz8HKnBq+U34dFysJ2KUh2Aboibw= +github.com/tliron/glsp v0.2.2 h1:IKPfwpE8Lu8yB6Dayta+IyRMAbTVunudeauEgjXBt+c= +github.com/tliron/glsp v0.2.2/go.mod h1:GMVWDNeODxHzmDPvYbYTCs7yHVaEATfYtXiYJ9w1nBg= +github.com/tliron/go-kutil v0.4.0 h1:5JwcBacgnqS3XyhwCWZKvq8ftlbVttNXnt+kfCH+Y2E= +github.com/tliron/go-kutil v0.4.0/go.mod h1:hpHVq+CP1uci2M208UEjPiPwsRsz/QweGBnLB3CaQ24= github.com/trivago/grok v1.0.0 h1:oV2ljyZT63tgXkmgEHg2U0jMqiKKuL0hkn49s6aRavQ= github.com/trivago/grok v1.0.0/go.mod h1:9t59xLInhrncYq9a3J7488NgiBZi5y5yC7bss+w4NHM= github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= @@ -186,13 +215,17 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/cli/app.go b/internal/cli/app.go index bb10830a7..24a63f6e2 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -13,6 +13,7 @@ import ( "github.com/redpanda-data/benthos/v4/internal/cli/blobl" "github.com/redpanda-data/benthos/v4/internal/cli/common" + "github.com/redpanda-data/benthos/v4/internal/cli/lsp" "github.com/redpanda-data/benthos/v4/internal/cli/studio" clitemplate "github.com/redpanda-data/benthos/v4/internal/cli/template" "github.com/redpanda-data/benthos/v4/internal/cli/test" @@ -137,6 +138,7 @@ func App(opts *common.CLIOpts) *cli.App { clitemplate.CliCommand(opts), blobl.CliCommand(opts), studio.CliCommand(opts), + lsp.CliCommand(opts), } { if _, exists := commandNames[c.Name]; !exists { // Only add standard commands that haven't been replaced with a diff --git a/internal/cli/lsp/linting/debounce.go b/internal/cli/lsp/linting/debounce.go new file mode 100644 index 000000000..563c20d99 --- /dev/null +++ b/internal/cli/lsp/linting/debounce.go @@ -0,0 +1,44 @@ +package linting + +import ( + "sync" + "time" + + "github.com/redpanda-data/benthos/v4/internal/cli/common" + "github.com/redpanda-data/benthos/v4/internal/docs" + "github.com/redpanda-data/benthos/v4/public/bloblang" +) + +type Debouncer struct { + Cfg docs.LintConfig + mu sync.Mutex + timers map[string]*time.Timer +} + +func NewDebouncer(opts *common.CLIOpts) *Debouncer { + lintConf := docs.NewLintConfig(opts.Environment) + lintConf.BloblangEnv = bloblang.XWrapEnvironment(opts.BloblEnvironment) + + return &Debouncer{ + timers: make(map[string]*time.Timer), + Cfg: lintConf, + } +} + +func (d *Debouncer) Debounce(uri string, delay time.Duration, lintAction func()) { + d.mu.Lock() + defer d.mu.Unlock() + + // If a timer already exists for this URI, stop it + if timer, ok := d.timers[uri]; ok { + timer.Stop() + } + + // Create a new timer + d.timers[uri] = time.AfterFunc(delay, func() { + lintAction() + d.mu.Lock() + delete(d.timers, uri) + d.mu.Unlock() + }) +} diff --git a/internal/cli/lsp/parser.go b/internal/cli/lsp/parser.go new file mode 100644 index 000000000..09373722b --- /dev/null +++ b/internal/cli/lsp/parser.go @@ -0,0 +1,71 @@ +package lsp + +import ( + "fmt" + + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/token" +) + +// TokenWithPath holds a token and its path in the YAML document +type TokenWithPath struct { + Token *token.Token + Path string +} + +func findTokenAtPosition(file *ast.File, line, column int) (*TokenWithPath, error) { + var result *TokenWithPath + + // Walk each document in the file + for _, doc := range file.Docs { + ast.Walk(&visitor{ + targetLine: line, + targetColumn: column, + result: &result, + }, doc.Body) + + if result != nil { + break + } + } + + if result != nil { + return result, nil + } + + return nil, fmt.Errorf("token not found at line %d, column %d", line, column) +} + +type visitor struct { + targetLine int + targetColumn int + result **TokenWithPath +} + +func (v *visitor) Visit(node ast.Node) ast.Visitor { + if node == nil { + return nil + } + + // Get the token from the node + tok := node.GetToken() + if tok != nil { + pos := tok.Position + + // Check if this token contains our target position + if pos.Line == v.targetLine { + // Check if the column is within the token's range + startCol := pos.Column + endCol := startCol + len(tok.Value) + + if v.targetColumn >= startCol && v.targetColumn < endCol { + *v.result = &TokenWithPath{ + Token: tok, + Path: node.GetPath(), + } + } + } + } + + return v +} diff --git a/internal/cli/lsp/server.go b/internal/cli/lsp/server.go new file mode 100644 index 000000000..2bb9f5b23 --- /dev/null +++ b/internal/cli/lsp/server.go @@ -0,0 +1,80 @@ +package lsp + +import ( + "github.com/redpanda-data/benthos/v4/internal/cli/common" + + "github.com/tliron/commonlog" + _ "github.com/tliron/commonlog/simple" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" + "github.com/tliron/glsp/server" + "github.com/urfave/cli/v2" +) + +const lsName = "Redpanda Connect Language Server" + +var ( + version string = "0.0.1" + handler protocol.Handler +) + +// CliCommand returns the CLI command for running the LSP server. +func CliCommand(opts *common.CLIOpts) *cli.Command { + return &cli.Command{ + Name: "lsp", + Usage: "Run the Language Server Protocol (LSP) server", + Description: ` +Starts the Redpanda Connect Language Server Protocol (LSP) server. +This provides IDE features like autocompletion and hover documentation.`, + Action: func(c *cli.Context) error { + Start(opts) + return nil + }, + } +} + +func Start(opts *common.CLIOpts) { + commonlog.Configure(2, nil) + state := newStateManager(opts) + + handler = protocol.Handler{ + Initialize: initialize, + Initialized: func(context *glsp.Context, params *protocol.InitializedParams) error { + return nil + }, + Shutdown: shutdown, + TextDocumentCompletion: state.completion, + TextDocumentDidChange: state.didChange, + TextDocumentDidOpen: state.openDocument, + TextDocumentDidClose: state.closeDocument, + TextDocumentHover: state.onHover, + DocumentLinkResolve: state.documentLink, + } + server := server.NewServer(&handler, lsName, true) + // server.RunStdio() + server.RunTCP("127.0.0.1:8085") +} + +func initialize(context *glsp.Context, params *protocol.InitializeParams) (any, error) { + commonlog.NewInfoMessage(0, "Initializing Redpanda Connect LSP...") + capabilities := handler.CreateServerCapabilities() + capabilities.TextDocumentSync = 1 // full + capabilities.HoverProvider = true + + trueVar := true + capabilities.CompletionProvider = &protocol.CompletionOptions{ + ResolveProvider: &trueVar, + } + + return protocol.InitializeResult{ + Capabilities: capabilities, + ServerInfo: &protocol.InitializeResultServerInfo{ + Name: lsName, + Version: &version, + }, + }, nil +} + +func shutdown(context *glsp.Context) error { + return nil +} diff --git a/internal/cli/lsp/state.go b/internal/cli/lsp/state.go new file mode 100644 index 000000000..7ec2cfed6 --- /dev/null +++ b/internal/cli/lsp/state.go @@ -0,0 +1,547 @@ +package lsp + +import ( + "errors" + "fmt" + "strings" + "time" + "unicode" + "unicode/utf8" + + "github.com/goccy/go-yaml/ast" + "github.com/goccy/go-yaml/parser" + "github.com/redpanda-data/benthos/v4/internal/cli/common" + "github.com/redpanda-data/benthos/v4/internal/cli/lsp/linting" + "github.com/redpanda-data/benthos/v4/internal/config" + "github.com/redpanda-data/benthos/v4/internal/config/schema" + "github.com/redpanda-data/benthos/v4/internal/docs" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +const ( + placeholder = "__placeholder__" + lintingDebounce = time.Second * 1 +) + +type stateManager struct { + documents map[string]string + opts *common.CLIOpts + schema schema.Full + linter *linting.Debouncer +} + +// newStateManager creates a new stateManger which is responsible for tracking opened connect.yaml files +// and inspecting them for various lsp tasks such as completion or documentation. +func newStateManager(opts *common.CLIOpts) stateManager { + schema := schema.New(opts.Version, opts.DateBuilt, opts.Environment, opts.BloblEnvironment) + schema.Config = opts.MainConfigSpecCtor() + + return stateManager{ + documents: map[string]string{}, + opts: opts, + schema: schema, + linter: linting.NewDebouncer(opts), + } +} + +func (s *stateManager) didChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error { + switch { + case len(params.ContentChanges) > 0: + if v, ok := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole); ok { + s.documents[params.TextDocument.URI] = v.Text + s.linter.Debounce(params.TextDocument.URI, lintingDebounce, func() { + s.publishDiagnostics(context, params.TextDocument.URI, v.Text) + }) + } + } + + return nil +} + +func countLeadingWhitespaceRunes(line string) int { + trimmed := strings.TrimLeftFunc(line, unicode.IsSpace) + return utf8.RuneCountInString(line) - utf8.RuneCountInString(trimmed) +} + +func cleanToken(t string) string { + x := strings.TrimSpace(t) + return strings.TrimSuffix(x, ":") +} + +type lineResult struct { + config string + value string +} + +func parseCurrentText(t string) *lineResult { + vals := strings.Split(t, ":") + if len(vals) > 1 { + line := &lineResult{ + config: strings.TrimSpace(vals[0]), + value: strings.TrimSpace(vals[1]), + } + return line + } + + return nil +} + +func (s *stateManager) completion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { + doc, ok := s.documents[params.TextDocument.URI] + if !ok { + return nil, nil + } + + var ( + parentToken string + currentText string + ) + + // When doing completion we capture the doc in a half edited state, meaning it could be invalid. + // To fix this, we replace the invalid line with a placeholder (ie: from "tab" to "tab: __placeholder__") + lines := strings.Split(doc, "\n") + if params.Position.Line >= 1 { + parentToken = cleanToken(lines[params.Position.Line-1]) + } + cursorLine := lines[params.Position.Line] + currentText = cleanToken(cursorLine) + + // capture whitespace so we can ensure correct indentation after replacement of broken line + prefix := strings.Repeat(" ", countLeadingWhitespaceRunes(cursorLine)) + lines[params.Position.Line] = fmt.Sprintf("%s%s: true", prefix, placeholder) + + // rejoin doc, it should now be valid for parsing + validDoc := strings.Join(lines, "\n") + "\n" + + // Parse valid document and get AST + astFile, err := parser.ParseBytes([]byte(validDoc), parser.ParseComments) + if err != nil { + return nil, err + } + + res, err := s.parseSpecByFile(astFile, int(params.Position.Line+1), int(params.Position.Character)) + if err != nil { + return nil, err + } + + token := normaliseToken(parentToken) + fmt.Printf("\n\n\n\nCleaned parent token: %s\n\n", token) + fmt.Printf("\n\n\nCurrent text is: %s\n\n\n", currentText) + + var ( + components []docs.ComponentSpec + completionItems []protocol.CompletionItem + ) + switch token { + case "-": + return completionItems, nil + case "": + format := protocol.InsertTextFormatSnippet + for _, v := range []string{"input", "processors", "cache", "buffer", "http", "metrics", "output"} { + insert := fmt.Sprintf("%s:\n\t", v) + completionItems = append(completionItems, protocol.CompletionItem{ + Label: v, + Documentation: "", + InsertText: &insert, + InsertTextFormat: &format, + }) + } + return completionItems, nil + case "input": + components = s.schema.Inputs + case "output": + components = s.schema.Outputs + case "processors": + components = s.schema.Processors + case "cache": + components = s.schema.Caches + case "buffer": + components = s.schema.Caches + case "metrics": + components = s.schema.Metrics + // case "http": + // components = s.schema. + } + + var additionalEdits []protocol.TextEdit + switch { + // components (http_server:, postgres_cdc: etc) + case len(components) > 0: + for _, component := range components { + var sb strings.Builder + format := protocol.InsertTextFormatSnippet + fmt.Fprintf(&sb, "%s:\n", component.Name) + cnt := 1 + for _, cfg := range component.Config.Children { + if cfg.CheckRequired() { + if cfg.Kind == docs.KindArray { + fmt.Fprintf(&sb, "\t%s:\n\t\t- ${%d}\n", cfg.Name, cnt) + } else { + fmt.Fprintf(&sb, "\t%s: ${%d}\n", cfg.Name, cnt) + } + cnt++ + } + } + insertText := sb.String() + completionItems = append(completionItems, protocol.CompletionItem{ + Label: component.Name, + Deprecated: &component.Config.IsDeprecated, + Documentation: component.Summary, + InsertText: &insertText, + AdditionalTextEdits: additionalEdits, + InsertTextFormat: &format, + // Kind: &kind, + }) + } + default: + // field (ie: connection_string:, stream_snapshot:) + line := parseCurrentText(currentText) + for _, opt := range res.cs.Config.Children { + // field value (ie: stream_snapshot: tr) + if line != nil && line.config == opt.Name { + kind := protocol.CompletionItemKindProperty + switch opt.Type { + case docs.FieldTypeString: + // if opt.Kind == docs.KindArray { + // insert := fmt.Sprintf("%s:\n\t", "boop") + // return []protocol.CompletionItem{{Label: insert, Deprecated: &opt.IsDeprecated, Documentation: opt.Description, InsertText: &insert, Kind: &kind}}, nil + // } else { + val := "" + return []protocol.CompletionItem{{Label: val, Deprecated: &opt.IsDeprecated, Documentation: opt.Description, InsertText: &val, Kind: &kind}}, nil + // } + case docs.FieldTypeBool: + t := "true" + f := "false" + completions := []protocol.CompletionItem{ + {Label: t, Deprecated: &opt.IsDeprecated, Documentation: opt.Description, InsertText: &t, Kind: &kind}, + {Label: f, Deprecated: &opt.IsDeprecated, Documentation: opt.Description, InsertText: &f, Kind: &kind}, + } + return completions, nil + } + + } + var ( + kind protocol.CompletionItemKind + insertText string + ) + + // fields (ie: include:, stream_snapshot: tr + switch opt.Type { + case docs.FieldTypeString: + if opt.Kind == docs.KindArray { + insertText = fmt.Sprintf("%s:\n - ", opt.Name) + } else { + insertText = fmt.Sprintf("%s: ", opt.Name) + if opt.Default != nil { + d := *opt.Default + if v, ok := d.(string); ok { + insertText += v + } + } + } + kind = protocol.CompletionItemKindText + case docs.FieldTypeObject: + insertText = fmt.Sprintf("%s:\n ", opt.Name) + // cnt := 1 + // for _, child := range opt.Children { + // insertText += fmt.Sprintf(" %s: ${%d}\n", child.Name, cnt) + // cnt++ + // } + kind = protocol.CompletionItemKindProperty + default: // includes "object" and others + insertText = fmt.Sprintf("%s: ", opt.Name) + kind = protocol.CompletionItemKindProperty + } + + completionItems = append(completionItems, protocol.CompletionItem{ + Label: opt.Name, + Deprecated: &opt.IsDeprecated, + Documentation: opt.Description, + InsertText: &insertText, + Kind: &kind, + }) + } + } + + return completionItems, nil +} + +func (s *stateManager) openDocument(context *glsp.Context, params *protocol.DidOpenTextDocumentParams) error { + s.documents[params.TextDocument.URI] = params.TextDocument.Text + // Lint when document opens + s.publishDiagnostics(context, params.TextDocument.URI, params.TextDocument.Text) + return nil +} + +func (s *stateManager) documentLink(context *glsp.Context, params *protocol.DocumentLink) (*protocol.DocumentLink, error) { + uri := protocol.DocumentUri("https://docs.redpanda.com/redpanda-connect/components/inputs/otlp_http/") + lnk := &protocol.DocumentLink{Target: &uri} + return lnk, nil +} + +func (s *stateManager) closeDocument(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error { + doc := params.TextDocument.URI + if _, ok := s.documents[doc]; ok { + // fmt.Printf("Closing document %s", doc) + delete(s.documents, params.TextDocument.URI) + // Clear diagnostics + context.Notify(protocol.ServerTextDocumentPublishDiagnostics, protocol.PublishDiagnosticsParams{ + URI: doc, + Diagnostics: []protocol.Diagnostic{}, + }) + } + return nil +} + +// publishDiagnostics lints the document and publishes diagnostics to the client +func (s *stateManager) publishDiagnostics(context *glsp.Context, uri string, doc string) { + lints, err := config.LintYAMLBytes(s.linter.Cfg, []byte(doc)) + + // fmt.Println(doc) + var diagnostics []protocol.Diagnostic + if err != nil { + // fmt.Printf("There's an error: %s\n", err) + // Handle parse errors + severity := protocol.DiagnosticSeverityError + diagnostics = append(diagnostics, protocol.Diagnostic{ + Range: protocol.Range{ + Start: protocol.Position{Line: 0, Character: 0}, + End: protocol.Position{Line: 0, Character: 0}, + }, + Severity: &severity, + Message: fmt.Sprintf("Failed to parse config: %v", err), + }) + } else { + // fmt.Println("There's no error") + // Convert lints to diagnostics + for _, lint := range lints { + severity := s.lintTypeToSeverity(lint.Type) + diagnostics = append(diagnostics, protocol.Diagnostic{ + Range: protocol.Range{ + Start: protocol.Position{Line: uint32(lint.Line - 1), Character: uint32(lint.Column - 1)}, + End: protocol.Position{Line: uint32(lint.Line - 1), Character: uint32(lint.Column)}, + }, + Severity: &severity, + Message: lint.What, + Source: stringPtr("connect"), + }) + } + } + + // Publish diagnostics to the client + p := protocol.PublishDiagnosticsParams{URI: uri, Diagnostics: make([]protocol.Diagnostic, 0)} + if len(diagnostics) > 0 { + p.Diagnostics = diagnostics + } + context.Notify(protocol.ServerTextDocumentPublishDiagnostics, p) +} + +// lintTypeToSeverity converts a lint type to an LSP diagnostic severity +func (s *stateManager) lintTypeToSeverity(lintType docs.LintType) protocol.DiagnosticSeverity { + switch lintType { + case docs.LintFailedRead, + docs.LintComponentMissing, + docs.LintComponentNotFound, + docs.LintMissing, + docs.LintExpectedArray, + docs.LintExpectedObject, + docs.LintExpectedScalar, + docs.LintBadBloblang: + return protocol.DiagnosticSeverityError + case docs.LintDeprecated, + docs.LintShouldOmit: + return protocol.DiagnosticSeverityWarning + case docs.LintMissingEnvVar, + docs.LintUnknown, + docs.LintInvalidOption: + return protocol.DiagnosticSeverityWarning + default: + return protocol.DiagnosticSeverityInformation + } +} + +// stringPtr returns a pointer to a string +func stringPtr(s string) *string { + return &s +} + +// func (s *state) onHover(id int, uri string, position protocol.Position) { +func (s *stateManager) onHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) { + doc, ok := s.documents[params.TextDocument.URI] + if !ok { + return nil, nil + } + + var ( + file *ast.File + err error + ) + if file, err = parser.ParseBytes([]byte(doc), parser.ParseComments); err != nil { + return nil, err + } + + // $.input.generate.interval + var token *TokenWithPath + if token, err = findTokenAtPosition(file, int(params.Position.Line+1), int(params.Position.Character+1)); err != nil { + return nil, err + } + + path := strings.Split(token.Path, ".") + // fmt.Printf("Token path: %s\n", token.Path) + + if path[0] != "$" { + return &protocol.Hover{Contents: ""}, nil + } + + var ( + components []docs.ComponentSpec + ) + + // $.input.generate.interval + // $.input.generate.mapping + var ( + cs docs.ComponentSpec + fs docs.FieldSpec + ) + path = path[1:] + cnt := 0 + for _, node := range path { + cnt++ + + token := normaliseToken(node) + switch token { + case "input": + components = s.schema.Inputs + case "output": + components = s.schema.Outputs + case "processors": + components = s.schema.Processors + case "cache": + components = s.schema.Caches + case "buffer": + components = s.schema.Caches + case "metrics": + components = s.schema.Metrics + } + + // components + for _, spec := range components { + if node == spec.Name { + cs = spec + break + } + } + + // children + if len(cs.Config.Children) > 0 { + for _, c := range cs.Config.Children { + if node == c.Name { + fs = c + break + } + } + } + + if cnt == len(path) { + switch node { + // configs (url, etc) + case fs.Name: + content := fmt.Sprintf("# Field: `%s (%s)`\n-----------------------------\n%s", fs.Name, fs.Type, fs.Description) + if len(fs.Examples) > 0 { + content += fmt.Sprintf("\n-----------------------------\n# Example: `%s`", fs.Examples[0]) + } + content += fmt.Sprintf("\n-----------------------------\n[%s field on docs.redpanda.com](https://docs.redpanda.com/redpanda-connect/components/%ss/%s#%s)\n", fs.Name, cs.Type, cs.Name, fs.Name) + return &protocol.Hover{Contents: content}, nil + // components (http_server, etc) + case cs.Name: + content := fmt.Sprintf("# Component: `%s (%s)`\n-----------------------------\n%s\n", cs.Name, cs.Type, cs.Summary) + content += fmt.Sprintf("\n-----------------------------\n[`%s` on docs.redpanda.com](https://docs.redpanda.com/redpanda-connect/components/%ss/%s)\n", cs.Name, cs.Type, cs.Name) + return &protocol.Hover{Contents: content}, nil + } + } + } + + return &protocol.Hover{Contents: ""}, nil +} + +type specResult struct { + cs *docs.ComponentSpec + fs *docs.FieldSpec +} + +func (s *stateManager) parseSpecByFile(file *ast.File, line, character int) (specResult, error) { + var ( + token *TokenWithPath + err error + ) + // $.input.generate.mapping + if token, err = findTokenAtPosition(file, line, character); err != nil { + return specResult{}, err + } + + path := strings.Split(token.Path, ".") + // fmt.Printf("Token path: %s\n", token.Path) + + if path[0] != "$" { + return specResult{}, errors.New("could not find token at position") + } + + var ( + components []docs.ComponentSpec + cs docs.ComponentSpec + fs docs.FieldSpec + ) + + path = path[1:] + cnt := 0 + for _, node := range path { + cnt++ + + token := normaliseToken(node) + // fmt.Printf("Cleaned token: %s\n", token) + switch token { + case "input": + components = s.schema.Inputs + case "output": + components = s.schema.Outputs + case "processors": + components = s.schema.Processors + case "cache": + components = s.schema.Caches + case "buffer": + components = s.schema.Caches + case "metrics": + components = s.schema.Metrics + } + + // components + for _, spec := range components { + if node == spec.Name { + cs = spec + break + } + } + + // children + if len(cs.Config.Children) > 0 { + for _, c := range cs.Config.Children { + if node == c.Name { + fs = c + break + } + } + } + } + + return specResult{&cs, &fs}, nil +} + +// normaliseToken cleans processors[0] or processors[1] and returns "processors" +func normaliseToken(token string) string { + const procToken = "processors" + if strings.HasPrefix(token, procToken) { + return procToken + } + return token +}