summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-05-20 22:00:00 -0400
committerDax Raad <[email protected]>2025-05-26 12:40:17 -0400
commit2860a2bb1a1f227c26b02f1325454ab79d6f6451 (patch)
tree2a52a5c159025c6ff6854af2995c24c4d7d46b77
parent9b564f0b73d099d40c79517213211ba81b3312c6 (diff)
downloadopencode-2860a2bb1a1f227c26b02f1325454ab79d6f6451.tar.gz
opencode-2860a2bb1a1f227c26b02f1325454ab79d6f6451.zip
sync
-rw-r--r--js/bun.lock127
-rw-r--r--js/package.json3
-rw-r--r--js/src/index.ts7
-rw-r--r--js/src/lsp/client.ts178
-rw-r--r--js/src/lsp/index.ts31
-rw-r--r--js/src/lsp/language.ts83
-rw-r--r--js/src/tool/edit.ts232
-rw-r--r--js/src/tool/tool.ts3
-rw-r--r--js/src/tool/view.ts8
-rw-r--r--js/src/util/log.ts1
-rw-r--r--js/src/util/scrap.ts5
11 files changed, 505 insertions, 173 deletions
diff --git a/js/bun.lock b/js/bun.lock
index df1b2a17f..46fb566ec 100644
--- a/js/bun.lock
+++ b/js/bun.lock
@@ -12,6 +12,9 @@
"clipanion": "^4.0.0-rc.4",
"hono": "^4.7.10",
"hono-openapi": "^0.4.8",
+ "ts-lsp-client": "^1.0.3",
+ "vscode-jsonrpc": "^8.2.1",
+ "vscode-languageclient": "8",
"zod": "^3.25.3",
"zod-openapi": "^4.2.4",
},
@@ -45,6 +48,8 @@
"@flystorage/local-fs": ["@flystorage/[email protected]", "", { "dependencies": { "@flystorage/dynamic-import": "^1.0.0", "@flystorage/file-storage": "^1.1.0", "file-type": "^20.5.0", "mime-types": "^3.0.1" } }, "sha512-dbErRhqmCv2UF0zPdeH7iVWuVeTWAJHuJD/mXDe2V370/SL7XIvdE3ditBHWC+1SzBKXJ0lkykOenwlum+oqIA=="],
+ "@hapi/bourne": ["@hapi/[email protected]", "", {}, "sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q=="],
+
"@hono/zod-validator": ["@hono/[email protected]", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-ds5bW6DCgAnNHP33E3ieSbaZFd5dkV52ZjyaXtGoR06APFrCtzAsKZxTHwOrJNBdXsi0e5wNwo5L4nVEVnJUdg=="],
"@jsdevtools/ono": ["@jsdevtools/[email protected]", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
@@ -79,10 +84,20 @@
"argparse": ["[email protected]", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+ "args": ["[email protected]", "", { "dependencies": { "camelcase": "5.0.0", "chalk": "2.4.2", "leven": "2.1.0", "mri": "1.1.4" } }, "sha512-h6k/zfFgusnv3i5TU08KQkVKuCPBtL/PWQbWkHUxvJrZ2nAyeaUupneemcrgn1xmqxPQsPIzwkUhOpoqPDRZuA=="],
+
+ "atomic-sleep": ["[email protected]", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
+
"auto-bind": ["[email protected]", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
+ "balanced-match": ["[email protected]", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
+
"bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="],
+ "camelcase": ["[email protected]", "", {}, "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA=="],
+
"chalk": ["[email protected]", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
"cli-boxes": ["[email protected]", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
@@ -97,26 +112,42 @@
"code-excerpt": ["[email protected]", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
+ "color-convert": ["[email protected]", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+ "color-name": ["[email protected]", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
"convert-to-spaces": ["[email protected]", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
"csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+ "dateformat": ["[email protected]", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
+
"debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
+ "duplexify": ["[email protected]", "", { "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", "stream-shift": "^1.0.2" } }, "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA=="],
+
"emoji-regex": ["[email protected]", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
+ "end-of-stream": ["[email protected]", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="],
+
"environment": ["[email protected]", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
"es-toolkit": ["[email protected]", "", {}, "sha512-OT3AxczYYd3W50bCj4V0hKoOAfqIy9tof0leNQYekEDxVKir3RTVTJOLij7VAe6fsCNsGhC0JqIkURpMXTCSEA=="],
"escape-string-regexp": ["[email protected]", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
+ "fast-redact": ["[email protected]", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="],
+
+ "fast-safe-stringify": ["[email protected]", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
+
"fflate": ["[email protected]", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
"file-type": ["[email protected]", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg=="],
"get-east-asian-width": ["[email protected]", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="],
+ "has-flag": ["[email protected]", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
"hono": ["[email protected]", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="],
"hono-openapi": ["[email protected]", "", { "dependencies": { "json-schema-walker": "^2.0.0" }, "peerDependencies": { "@hono/arktype-validator": "^2.0.0", "@hono/effect-validator": "^1.2.0", "@hono/typebox-validator": "^0.2.0 || ^0.3.0", "@hono/valibot-validator": "^0.5.1", "@hono/zod-validator": "^0.4.1", "@sinclair/typebox": "^0.34.9", "@valibot/to-json-schema": "^1.0.0-beta.3", "arktype": "^2.0.0", "effect": "^3.11.3", "hono": "^4.6.13", "openapi-types": "^12.1.3", "valibot": "^1.0.0-beta.9", "zod": "^3.23.8", "zod-openapi": "^4.0.0" }, "optionalPeers": ["@hono/arktype-validator", "@hono/effect-validator", "@hono/typebox-validator", "@hono/valibot-validator", "@hono/zod-validator", "@sinclair/typebox", "@valibot/to-json-schema", "arktype", "effect", "hono", "valibot", "zod", "zod-openapi"] }, "sha512-LYr5xdtD49M7hEAduV1PftOMzuT8ZNvkyWfh1DThkLsIr4RkvDb12UxgIiFbwrJB6FLtFXLoOZL9x4IeDk2+VA=="],
@@ -125,6 +156,8 @@
"indent-string": ["[email protected]", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
+ "inherits": ["[email protected]", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
"ink": ["[email protected]", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.22.0", "indent-string": "^5.0.0", "is-in-ci": "^1.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.29.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=18.0.0", "react": ">=18.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg=="],
"ink-text-input": ["[email protected]", "", { "dependencies": { "chalk": "^5.3.0", "type-fest": "^4.18.2" }, "peerDependencies": { "ink": ">=5", "react": ">=18" } }, "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw=="],
@@ -133,14 +166,22 @@
"is-in-ci": ["[email protected]", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg=="],
+ "jmespath": ["[email protected]", "", {}, "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w=="],
+
+ "joycon": ["[email protected]", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
+
"js-tokens": ["[email protected]", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
+ "json-rpc-2.0": ["[email protected]", "", {}, "sha512-asnLgC1qD5ytP+fvBP8uL0rvj+l8P6iYICbzZ8dVxCpESffVjzA7KkYkbKCIbavs7cllwH1ZUaNtJwphdeRqpg=="],
+
"json-schema": ["[email protected]", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
"json-schema-walker": ["[email protected]", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", "clone": "^2.1.2" } }, "sha512-nXN2cMky0Iw7Af28w061hmxaPDaML5/bQD9nwm1lOoIKEGjHcRGxqWe4MfrkYThYAPjSUhmsp4bJNoLAyVn9Xw=="],
+ "leven": ["[email protected]", "", {}, "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA=="],
+
"loose-envify": ["[email protected]", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"mime-db": ["[email protected]", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
@@ -149,8 +190,16 @@
"mimic-fn": ["[email protected]", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
+ "minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
+
+ "mri": ["[email protected]", "", {}, "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w=="],
+
"ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+ "on-exit-leak-free": ["[email protected]", "", {}, "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg=="],
+
+ "once": ["[email protected]", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
"onetime": ["[email protected]", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"openapi-types": ["[email protected]", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
@@ -159,28 +208,72 @@
"peek-readable": ["[email protected]", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="],
+ "pino": ["[email protected]", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.0.0", "on-exit-leak-free": "^0.2.0", "pino-abstract-transport": "v0.5.0", "pino-std-serializers": "^4.0.0", "process-warning": "^1.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.1.0", "safe-stable-stringify": "^2.1.0", "sonic-boom": "^2.2.1", "thread-stream": "^0.15.1" }, "bin": { "pino": "bin.js" } }, "sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg=="],
+
+ "pino-abstract-transport": ["[email protected]", "", { "dependencies": { "duplexify": "^4.1.2", "split2": "^4.0.0" } }, "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ=="],
+
+ "pino-pretty": ["[email protected]", "", { "dependencies": { "@hapi/bourne": "^2.0.0", "args": "^5.0.1", "chalk": "^4.0.0", "dateformat": "^4.5.1", "fast-safe-stringify": "^2.0.7", "jmespath": "^0.15.0", "joycon": "^3.0.0", "pump": "^3.0.0", "readable-stream": "^3.6.0", "rfdc": "^1.3.0", "split2": "^3.1.1", "strip-json-comments": "^3.1.1" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-Zj+0TVdYKkAAIx9EUCL5e4TttwgsaFvJh2ceIMQeFCY8ak9tseEZQGSgpvyjEj1/iIVGIh5tdhkGEQWSMILKHA=="],
+
+ "pino-std-serializers": ["[email protected]", "", {}, "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q=="],
+
+ "process-warning": ["[email protected]", "", {}, "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q=="],
+
+ "pump": ["[email protected]", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw=="],
+
+ "quick-format-unescaped": ["[email protected]", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
+
"react": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
"react-reconciler": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg=="],
+ "readable-stream": ["[email protected]", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
+ "real-require": ["[email protected]", "", {}, "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg=="],
+
"restore-cursor": ["[email protected]", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
+ "rfdc": ["[email protected]", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
+
+ "safe-buffer": ["[email protected]", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
+ "safe-stable-stringify": ["[email protected]", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
+
"scheduler": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
+ "semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+
"signal-exit": ["[email protected]", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"slice-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg=="],
+ "sonic-boom": ["[email protected]", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg=="],
+
+ "split2": ["[email protected]", "", { "dependencies": { "readable-stream": "^3.0.0" } }, "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg=="],
+
"stack-utils": ["[email protected]", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
+ "stream-shift": ["[email protected]", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="],
+
"string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
+ "string_decoder": ["[email protected]", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+
"strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
+ "strip-json-comments": ["[email protected]", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
+
"strtok3": ["[email protected]", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^7.0.0" } }, "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg=="],
+ "supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "thread-stream": ["[email protected]", "", { "dependencies": { "real-require": "^0.1.0" } }, "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA=="],
+
"token-types": ["[email protected]", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
+ "ts-lsp-client": ["[email protected]", "", { "dependencies": { "json-rpc-2.0": "^1.7.0", "pino": "^7.0.5", "pino-pretty": "^5.1.3", "tslib": "~2.6.2" } }, "sha512-0ItrsqvNUM9KNFGbeT1N8jSi9gvasGOvxJUXjGf4P2TX0w250AUWLeRStaSrQbYcFDshDtE5d4BshUmYwodDgw=="],
+
+ "tslib": ["[email protected]", "", {}, "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="],
+
"typanion": ["[email protected]", "", {}, "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug=="],
"type-fest": ["[email protected]", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
@@ -191,10 +284,22 @@
"undici-types": ["[email protected]", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
+ "util-deprecate": ["[email protected]", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+
+ "vscode-jsonrpc": ["[email protected]", "", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="],
+
+ "vscode-languageclient": ["[email protected]", "", { "dependencies": { "minimatch": "^5.1.0", "semver": "^7.3.7", "vscode-languageserver-protocol": "3.17.3" } }, "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing=="],
+
+ "vscode-languageserver-protocol": ["[email protected]", "", { "dependencies": { "vscode-jsonrpc": "8.1.0", "vscode-languageserver-types": "3.17.3" } }, "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA=="],
+
+ "vscode-languageserver-types": ["[email protected]", "", {}, "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="],
+
"widest-line": ["[email protected]", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
"wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="],
+ "wrappy": ["[email protected]", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
"ws": ["[email protected]", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
"yoga-layout": ["[email protected]", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
@@ -205,8 +310,30 @@
"zod-to-json-schema": ["[email protected]", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
+ "args/chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+
"cli-truncate/slice-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
+ "pino-abstract-transport/split2": ["[email protected]", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
+
+ "pino-pretty/chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
"slice-ansi/is-fullwidth-code-point": ["[email protected]", "", { "dependencies": { "get-east-asian-width": "^1.0.0" } }, "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA=="],
+
+ "vscode-languageserver-protocol/vscode-jsonrpc": ["[email protected]", "", {}, "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw=="],
+
+ "args/chalk/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
+
+ "args/chalk/escape-string-regexp": ["[email protected]", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+ "args/chalk/supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
+
+ "pino-pretty/chalk/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "args/chalk/ansi-styles/color-convert": ["[email protected]", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
+
+ "args/chalk/supports-color/has-flag": ["[email protected]", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
+
+ "args/chalk/ansi-styles/color-convert/color-name": ["[email protected]", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
}
}
diff --git a/js/package.json b/js/package.json
index 4e8fc8dcd..0d03b4213 100644
--- a/js/package.json
+++ b/js/package.json
@@ -26,6 +26,9 @@
"clipanion": "^4.0.0-rc.4",
"hono": "^4.7.10",
"hono-openapi": "^0.4.8",
+ "ts-lsp-client": "^1.0.3",
+ "vscode-jsonrpc": "^8.2.1",
+ "vscode-languageclient": "8",
"zod": "^3.25.3",
"zod-openapi": "^4.2.4"
}
diff --git a/js/src/index.ts b/js/src/index.ts
index 609abf14b..7d114c642 100644
--- a/js/src/index.ts
+++ b/js/src/index.ts
@@ -1,10 +1,11 @@
import { App } from "./app";
import { Server } from "./server/server";
-import { Cli, Command, Option, runExit } from "clipanion";
+import { Cli, Command, Option } from "clipanion";
import fs from "fs/promises";
import path from "path";
import { Bus } from "./bus";
import { Session } from "./session/session";
+import { LSP } from "./lsp";
const cli = new Cli({
binaryLabel: `opencode`,
@@ -25,6 +26,7 @@ cli.register(
}
},
);
+
cli.register(
class extends Command {
static paths = [["generate"]];
@@ -71,6 +73,9 @@ cli.register(
"tool:",
part.toolInvocation.toolName,
part.toolInvocation.args,
+ part.toolInvocation.state === "result"
+ ? part.toolInvocation.result
+ : "",
);
}
}
diff --git a/js/src/lsp/client.ts b/js/src/lsp/client.ts
new file mode 100644
index 000000000..e6cfdb2eb
--- /dev/null
+++ b/js/src/lsp/client.ts
@@ -0,0 +1,178 @@
+import { spawn } from "child_process";
+import path from "path";
+import {
+ createMessageConnection,
+ Disposable,
+ StreamMessageReader,
+ StreamMessageWriter,
+} from "vscode-jsonrpc/node";
+import { App } from "../app";
+import { Log } from "../util/log";
+import { LANGUAGE_EXTENSIONS } from "./language";
+
+export namespace LSPClient {
+ const log = Log.create({ service: "lsp.client" });
+
+ export type Info = Awaited<ReturnType<typeof create>>;
+
+ export async function create(input: { cmd: string[] }) {
+ log.info("starting client", input);
+ let version = 0;
+
+ const app = await App.use();
+ const [command, ...args] = input.cmd;
+ const server = spawn(command, args, {
+ stdio: ["pipe", "pipe", "pipe"],
+ cwd: app.root,
+ });
+
+ const connection = createMessageConnection(
+ new StreamMessageReader(server.stdout),
+ new StreamMessageWriter(server.stdin),
+ );
+
+ const diagnostics = new Map<string, any>();
+ connection.onNotification("textDocument/publishDiagnostics", (params) => {
+ log.info("textDocument/publishDiagnostics", {
+ path: new URL(params.uri).pathname,
+ });
+ diagnostics.set(new URL(params.uri).pathname, params.diagnostics);
+ });
+ connection.listen();
+
+ await connection.sendRequest("initialize", {
+ processId: server.pid,
+ initializationOptions: {
+ workspaceFolders: [
+ {
+ name: "workspace",
+ uri: "file://" + app.root,
+ },
+ ],
+ tsserver: {
+ path: require.resolve("typescript/lib/tsserver.js"),
+ },
+ },
+ capabilities: {
+ workspace: {
+ configuration: true,
+ didChangeConfiguration: {
+ dynamicRegistration: true,
+ },
+ didChangeWatchedFiles: {
+ dynamicRegistration: true,
+ relativePatternSupport: true,
+ },
+ },
+ textDocument: {
+ synchronization: {
+ dynamicRegistration: true,
+ didSave: true,
+ },
+ completion: {
+ completionItem: {},
+ },
+ codeLens: {
+ dynamicRegistration: true,
+ },
+ documentSymbol: {},
+ codeAction: {
+ codeActionLiteralSupport: {
+ codeActionKind: {
+ valueSet: [],
+ },
+ },
+ },
+ publishDiagnostics: {
+ versionSupport: true,
+ },
+ semanticTokens: {
+ requests: {
+ range: {},
+ full: {},
+ },
+ tokenTypes: [],
+ tokenModifiers: [],
+ formats: [],
+ },
+ },
+ window: {},
+ },
+ });
+ await connection.sendNotification("initialized", {});
+ log.info("initialized");
+
+ const result = {
+ get connection() {
+ return connection;
+ },
+ notify: {
+ async open(input: { path: string }) {
+ log.info("textDocument/didOpen", input);
+ diagnostics.delete(input.path);
+ const text = await Bun.file(input.path).text();
+ const languageId = LANGUAGE_EXTENSIONS[path.extname(input.path)];
+ await connection.sendNotification("textDocument/didOpen", {
+ textDocument: {
+ uri: `file://` + input.path,
+ languageId,
+ version: 1,
+ text: text,
+ },
+ });
+ },
+ async change(input: { path: string }) {
+ log.info("textDocument/didChange", input);
+ diagnostics.delete(input.path);
+ const text = await Bun.file(input.path).text();
+ version++;
+ await connection.sendNotification("textDocument/didChange", {
+ textDocument: {
+ uri: `file://` + input.path,
+ version: Date.now(),
+ },
+ contentChanges: [
+ {
+ text,
+ },
+ ],
+ });
+ },
+ },
+ get diagnostics() {
+ return diagnostics;
+ },
+ async refreshDiagnostics(input: { path: string }) {
+ log.info("refreshing diagnostics", input);
+ let notif: Disposable | undefined;
+ return await Promise.race([
+ new Promise<void>(async (resolve) => {
+ notif = connection.onNotification(
+ "textDocument/publishDiagnostics",
+ (params) => {
+ log.info("refreshed diagnostics", input);
+ if (new URL(params.uri).pathname === input.path) {
+ diagnostics.set(
+ new URL(params.uri).pathname,
+ params.diagnostics,
+ );
+ resolve();
+ notif?.dispose();
+ }
+ },
+ );
+ await result.notify.change(input);
+ }),
+ new Promise<void>((resolve) =>
+ setTimeout(() => {
+ notif?.dispose();
+ resolve();
+ }, 5000),
+ ),
+ ]);
+ },
+ };
+
+ return result;
+ }
+}
diff --git a/js/src/lsp/index.ts b/js/src/lsp/index.ts
new file mode 100644
index 000000000..ac200fc51
--- /dev/null
+++ b/js/src/lsp/index.ts
@@ -0,0 +1,31 @@
+import { App } from "../app";
+import { Log } from "../util/log";
+import { LSPClient } from "./client";
+
+export namespace LSP {
+ const log = Log.create({ service: "lsp" });
+
+ const state = App.state("lsp", async () => {
+ const clients = new Map<string, LSPClient.Info>();
+
+ clients.set(
+ "typescript",
+ await LSPClient.create({
+ cmd: ["bun", "x", "typescript-language-server", "--stdio"],
+ }),
+ );
+
+ return {
+ clients,
+ diagnostics: new Map<string, any>(),
+ };
+ });
+
+ export async function run<T>(
+ input: (client: LSPClient.Info) => Promise<T>,
+ ): Promise<T[]> {
+ const clients = await state().then((x) => [...x.clients.values()]);
+ const tasks = clients.map((x) => input(x));
+ return Promise.all(tasks);
+ }
+}
diff --git a/js/src/lsp/language.ts b/js/src/lsp/language.ts
new file mode 100644
index 000000000..0a9bc0f7f
--- /dev/null
+++ b/js/src/lsp/language.ts
@@ -0,0 +1,83 @@
+export const LANGUAGE_EXTENSIONS: Record<string, string> = {
+ ".abap": "abap",
+ ".bat": "bat",
+ ".bib": "bibtex",
+ ".bibtex": "bibtex",
+ ".clj": "clojure",
+ ".coffee": "coffeescript",
+ ".c": "c",
+ ".cpp": "cpp",
+ ".cxx": "cpp",
+ ".cc": "cpp",
+ ".c++": "cpp",
+ ".cs": "csharp",
+ ".css": "css",
+ ".d": "d",
+ ".pas": "pascal",
+ ".pascal": "pascal",
+ ".diff": "diff",
+ ".patch": "diff",
+ ".dart": "dart",
+ ".dockerfile": "dockerfile",
+ ".ex": "elixir",
+ ".exs": "elixir",
+ ".erl": "erlang",
+ ".hrl": "erlang",
+ ".fs": "fsharp",
+ ".fsi": "fsharp",
+ ".fsx": "fsharp",
+ ".fsscript": "fsharp",
+ ".gitcommit": "git-commit",
+ ".gitrebase": "git-rebase",
+ ".go": "go",
+ ".groovy": "groovy",
+ ".hbs": "handlebars",
+ ".handlebars": "handlebars",
+ ".hs": "haskell",
+ ".html": "html",
+ ".htm": "html",
+ ".ini": "ini",
+ ".java": "java",
+ ".js": "javascript",
+ ".jsx": "javascriptreact",
+ ".json": "json",
+ ".tex": "latex",
+ ".latex": "latex",
+ ".less": "less",
+ ".lua": "lua",
+ ".makefile": "makefile",
+ makefile: "makefile",
+ ".md": "markdown",
+ ".markdown": "markdown",
+ ".m": "objective-c",
+ ".mm": "objective-cpp",
+ ".pl": "perl",
+ ".pm": "perl6",
+ ".php": "php",
+ ".ps1": "powershell",
+ ".psm1": "powershell",
+ ".pug": "jade",
+ ".jade": "jade",
+ ".py": "python",
+ ".r": "r",
+ ".cshtml": "razor",
+ ".razor": "razor",
+ ".rb": "ruby",
+ ".rs": "rust",
+ ".scss": "scss",
+ ".sass": "sass",
+ ".scala": "scala",
+ ".shader": "shaderlab",
+ ".sh": "shellscript",
+ ".bash": "shellscript",
+ ".zsh": "shellscript",
+ ".ksh": "shellscript",
+ ".sql": "sql",
+ ".swift": "swift",
+ ".ts": "typescript",
+ ".tsx": "typescriptreact",
+ ".xml": "xml",
+ ".xsl": "xsl",
+ ".yaml": "yaml",
+ ".yml": "yaml",
+};
diff --git a/js/src/tool/edit.ts b/js/src/tool/edit.ts
index 24bf29bcb..e83564f0f 100644
--- a/js/src/tool/edit.ts
+++ b/js/src/tool/edit.ts
@@ -4,6 +4,7 @@ import * as path from "path";
import { Log } from "../util/log";
import { Tool } from "./tool";
import { FileTimes } from "./util/file-times";
+import { LSP } from "../lsp";
const log = Log.create({ service: "tool.edit" });
@@ -78,7 +79,7 @@ Before using this tool:
- Use the LS tool to verify the parent directory exists and is the correct location
To make a file edit, provide the following:
-1. file_path: The absolute path to the file to modify (must be absolute, not relative)
+1. file_path: The relative path to the file to modify (must be relative, not absolute)
2. old_string: The text to replace (must be unique within the file, and must match the file contents exactly, including all whitespace and indentation)
3. new_string: The edited text to replace the old_string
@@ -112,7 +113,7 @@ WARNING: If you do not follow these requirements:
When making edits:
- Ensure the edit results in idiomatic, correct code
- Do not leave the code in a broken state
- - Always use absolute file paths (starting with /)
+ - Always use relative file paths
Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`;
@@ -134,21 +135,68 @@ export const EditTool = Tool.define({
filePath = path.join(process.cwd(), filePath);
}
- // Handle different operations based on parameters
- if (params.old_string === "") {
- return {
- output: createNewFile(filePath, params.new_string),
- };
- }
+ await (async () => {
+ if (params.old_string === "") {
+ await createNewFile(filePath, params.new_string);
+ return;
+ }
- if (params.new_string === "") {
- return {
- output: deleteContent(filePath, params.old_string),
- };
+ const read = FileTimes.get(filePath);
+ if (!read)
+ throw new Error(
+ `You must read the file ${filePath} before editing it. Use the View tool first`,
+ );
+ const file = Bun.file(filePath);
+ if (!(await file.exists())) throw new Error(`File ${filePath} not found`);
+ const stats = await file.stat();
+ if (stats.isDirectory())
+ throw new Error(`Path is a directory, not a file: ${filePath}`);
+ if (stats.mtime.getTime() > read.getTime())
+ throw new Error(
+ `File ${filePath} has been modified since it was last read.\nLast modification: ${read.toISOString()}\nLast read: ${stats.mtime.toISOString()}\n\nPlease read the file again before modifying it.`,
+ );
+
+ const content = await file.text();
+ const index = content.indexOf(params.old_string);
+ if (index === -1)
+ throw new Error(
+ `old_string not found in file. Make sure it matches exactly, including whitespace and line breaks`,
+ );
+ const lastIndex = content.lastIndexOf(params.old_string);
+ if (index !== lastIndex)
+ throw new Error(
+ `old_string appears multiple times in the file. Please provide more context to ensure a unique match`,
+ );
+
+ const newContent =
+ content.substring(0, index) +
+ params.new_string +
+ content.substring(index + params.old_string.length);
+
+ console.log(newContent);
+ await file.write(newContent);
+ })();
+
+ FileTimes.write(filePath);
+ FileTimes.read(filePath);
+
+ let output = "";
+ await LSP.run((client) => client.refreshDiagnostics({ path: filePath }));
+ const diagnostics = await LSP.run(async (client) => client.diagnostics);
+ for (const diagnostic of diagnostics) {
+ for (const [file, params] of diagnostic.entries()) {
+ if (params.length === 0) continue;
+ if (file === filePath) {
+ output += `\nThis file has errors, please fix\n<file_diagnostics>\n${JSON.stringify(params)}\n</file_diagnostics>\n`;
+ continue;
+ }
+ output += `\n<project_diagnostics>\n${JSON.stringify(params)}\n</project_diagnostics>\n`;
+ }
}
+ console.log(output);
return {
- output: replaceContent(filePath, params.old_string, params.new_string),
+ output,
};
},
});
@@ -186,160 +234,4 @@ async function createNewFile(
}
}
-async function deleteContent(
- filePath: string,
- oldString: string,
-): Promise<string> {
- try {
- // Check if file exists
- let fileStats;
- try {
- fileStats = fs.statSync(filePath);
- if (fileStats.isDirectory()) {
- throw new Error(`Path is a directory, not a file: ${filePath}`);
- }
- } catch (err: any) {
- if (err.code === "ENOENT") {
- throw new Error(`File not found: ${filePath}`);
- }
- throw err;
- }
-
- const lastReadTime = FileTimes.get(filePath);
- if (!lastReadTime) {
- throw new Error(
- "You must read the file before editing it. Use the View tool first",
- );
- }
-
- const modTime = fileStats.mtime;
- if (modTime > lastReadTime) {
- throw new Error(
- `File ${filePath} has been modified since it was last read (mod time: ${modTime.toISOString()}, last read: ${lastReadTime.toISOString()})`,
- );
- }
-
- const oldContent = fs.readFileSync(filePath, "utf8");
- const index = oldContent.indexOf(oldString);
- if (index === -1) {
- throw new Error(
- "old_string not found in file. Make sure it matches exactly, including whitespace and line breaks",
- );
- }
-
- const lastIndex = oldContent.lastIndexOf(oldString);
- if (index !== lastIndex) {
- throw new Error(
- "old_string appears multiple times in the file. Please provide more context to ensure a unique match",
- );
- }
-
- const newContent =
- oldContent.substring(0, index) +
- oldContent.substring(index + oldString.length);
-
- const { diff, additions, removals } = generateDiff(
- oldContent,
- newContent,
- filePath,
- );
-
- // Write the file
- fs.writeFileSync(filePath, newContent);
-
- FileTimes.write(filePath);
- FileTimes.read(filePath);
-
- return `Content deleted from file: ${filePath}`;
- } catch (err: any) {
- throw new Error(`Failed to delete content: ${err.message}`);
- }
-}
-
-async function replaceContent(
- filePath: string,
- oldString: string,
- newString: string,
-): Promise<string> {
- try {
- // Check if file exists
- let fileStats;
- try {
- fileStats = fs.statSync(filePath);
- if (fileStats.isDirectory()) {
- throw new Error(`Path is a directory, not a file: ${filePath}`);
- }
- } catch (err: any) {
- if (err.code === "ENOENT") {
- throw new Error(`File not found: ${filePath}`);
- }
- throw err;
- }
-
- // Check if file has been read before
- const lastReadTime = getLastReadTime(filePath);
- if (!lastReadTime) {
- throw new Error(
- "You must read the file before editing it. Use the View tool first",
- );
- }
-
- // Check if file has been modified since last read
- const modTime = fileStats.mtime;
- if (modTime > lastReadTime) {
- throw new Error(
- `File ${filePath} has been modified since it was last read (mod time: ${modTime.toISOString()}, last read: ${lastReadTime.toISOString()})`,
- );
- }
-
- // Read the file content
- const oldContent = fs.readFileSync(filePath, "utf8");
-
- // Find the string to replace
- const index = oldContent.indexOf(oldString);
- if (index === -1) {
- throw new Error(
- "old_string not found in file. Make sure it matches exactly, including whitespace and line breaks",
- );
- }
-
- // Check if the string appears multiple times
- const lastIndex = oldContent.lastIndexOf(oldString);
- if (index !== lastIndex) {
- throw new Error(
- "old_string appears multiple times in the file. Please provide more context to ensure a unique match",
- );
- }
-
- // Create the new content
- const newContent =
- oldContent.substring(0, index) +
- newString +
- oldContent.substring(index + oldString.length);
-
- // Check if content actually changed
- if (oldContent === newContent) {
- throw new Error(
- "new content is the same as old content. No changes made.",
- );
- }
-
- // Generate diff
- const { diff, additions, removals } = generateDiff(
- oldContent,
- newContent,
- filePath,
- );
-
- // Write the file
- fs.writeFileSync(filePath, newContent);
-
- FileTimes.write(filePath);
- FileTimes.read(filePath);
-
- return `Content replaced in file: ${filePath}`;
- } catch (err: any) {
- throw new Error(`Failed to replace content: ${err.message}`);
- }
-}
-
+function getFile(filePath: string) {}
diff --git a/js/src/tool/tool.ts b/js/src/tool/tool.ts
index 748025707..47a3918d4 100644
--- a/js/src/tool/tool.ts
+++ b/js/src/tool/tool.ts
@@ -40,6 +40,9 @@ export namespace Tool {
output: result.output,
};
} catch (e: any) {
+ log.error("error", {
+ msg: e.toString(),
+ });
return "An error occurred: " + e.toString();
}
},
diff --git a/js/src/tool/view.ts b/js/src/tool/view.ts
index ba6fabbfa..2da0ab0f3 100644
--- a/js/src/tool/view.ts
+++ b/js/src/tool/view.ts
@@ -2,6 +2,8 @@ import { z } from "zod";
import * as fs from "fs";
import * as path from "path";
import { Tool } from "./tool";
+import { LSP } from "../lsp";
+import { FileTimes } from "./util/file-times";
const MAX_READ_SIZE = 250 * 1024;
const DEFAULT_READ_LIMIT = 2000;
@@ -117,8 +119,11 @@ export const ViewTool = Tool.define({
}
output += "\n</file>";
+ await LSP.run((client) => client.notify.open({ path: filePath }));
+ FileTimes.read(filePath);
+
return {
- output: output,
+ output,
};
},
});
@@ -143,4 +148,3 @@ function isImageFile(filePath: string): string | false {
return false;
}
}
-
diff --git a/js/src/util/log.ts b/js/src/util/log.ts
index c43514ef6..d05f42dc2 100644
--- a/js/src/util/log.ts
+++ b/js/src/util/log.ts
@@ -12,6 +12,7 @@ export namespace Log {
};
export function file(directory: string) {
+ return;
const out = Bun.file(
path.join(AppPath.data(directory), "opencode.out.log"),
).writer();
diff --git a/js/src/util/scrap.ts b/js/src/util/scrap.ts
new file mode 100644
index 000000000..16005acdc
--- /dev/null
+++ b/js/src/util/scrap.ts
@@ -0,0 +1,5 @@
+export const foo: string = "42";
+
+export function dummyFunction(): void {
+ console.log("This is a dummy function");
+}