codemirror lsp highlighter (#2806)
* tokenizer Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) udates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) updates Signed-off-by: Jess Frazelle <github@jessfraz.com> A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> more cleaniup Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> dont react to non relevant events Signed-off-by: Jess Frazelle <github@jessfraz.com> A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) faster Signed-off-by: Jess Frazelle <github@jessfraz.com> cleanup code Signed-off-by: Jess Frazelle <github@jessfraz.com> defer Signed-off-by: Jess Frazelle <github@jessfraz.com> more events Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> user events Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> udpates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates ; Signed-off-by: Jess Frazelle <github@jessfraz.com> upfates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> make highlighting code blocks easier Signed-off-by: Jess Frazelle <github@jessfraz.com> improvements Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> better builds Signed-off-by: Jess Frazelle <github@jessfraz.com> remove weird hacks Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> better checks Signed-off-by: Jess Frazelle <github@jessfraz.com> A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) make release builds in prod (#2839) Update package.json udpates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fix some tests Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) updates Signed-off-by: Jess Frazelle <github@jessfraz.com> better timing Signed-off-by: Jess Frazelle <github@jessfraz.com> tweak delay Signed-off-by: Jess Frazelle <github@jessfraz.com> A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) upfates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> ifxup Signed-off-by: Jess Frazelle <github@jessfraz.com> udpates Signed-off-by: Jess Frazelle <github@jessfraz.com> udpates Signed-off-by: Jess Frazelle <github@jessfraz.com> A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) udpates Signed-off-by: Jess Frazelle <github@jessfraz.com> wait for the lsp for all screenshots so consistent Signed-off-by: Jess Frazelle <github@jessfraz.com> better playwright Signed-off-by: Jess Frazelle <github@jessfraz.com> A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> Call core dump from the bug reporting button(s) (#2783) * Add coredump to refresh button - this one indicates that there should be something like a core dump that is triggered. * Added lower right control bug report button - included custom toasts for bug reporting, supports fallback bug reporting when app cannot generate a core dump better keymaps Signed-off-by: Jess Frazelle <github@jessfraz.com> emptu in comment Signed-off-by: Jess Frazelle <github@jessfraz.com> fix logs Signed-off-by: Jess Frazelle <github@jessfraz.com> fxes Signed-off-by: Jess Frazelle <github@jessfraz.com> add a test for tab to autocomplete Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> better Signed-off-by: Jess Frazelle <github@jessfraz.com> printl Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * upfates Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * empty * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
7
.vscode/settings.json
vendored
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"cSpell.words": [
|
|
||||||
"geos"
|
|
||||||
],
|
|
||||||
"editor.tabSize": 2,
|
|
||||||
"editor.insertSpaces": true,
|
|
||||||
}
|
|
@ -82,7 +82,7 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
test.skip(process.platform === 'darwin', 'Can moving camera')
|
test.skip(process.platform === 'darwin', 'Can moving camera')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
@ -404,7 +404,7 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -540,7 +540,6 @@ test('if you click the format button it formats your code', async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
@ -580,18 +579,8 @@ test('hover over functions shows function description', async ({ page }) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
|
||||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
|
||||||
// but that doesn't seem to make it to the console for macos/safari :(
|
|
||||||
if (message.text().includes('start kcl lsp')) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await lspStartPromise
|
|
||||||
|
|
||||||
// check no error to begin with
|
// check no error to begin with
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
@ -633,18 +622,8 @@ test('if you use the format keyboard binding it formats your code', async ({
|
|||||||
localStorage.setItem('disableAxis', 'true')
|
localStorage.setItem('disableAxis', 'true')
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
|
||||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
|
||||||
// but that doesn't seem to make it to the console for macos/safari :(
|
|
||||||
if (message.text().includes('start kcl lsp')) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await lspStartPromise
|
|
||||||
|
|
||||||
// check no error to begin with
|
// check no error to begin with
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
@ -669,8 +648,9 @@ test('if you use the format keyboard binding it formats your code', async ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('ensure the Zoo logo is not a link in browser app', async ({ page }) => {
|
test('ensure the Zoo logo is not a link in browser app', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
const zooLogo = page.locator('[data-testid="app-logo"]')
|
const zooLogo = page.locator('[data-testid="app-logo"]')
|
||||||
// Make sure it's not a link
|
// Make sure it's not a link
|
||||||
@ -680,7 +660,6 @@ test('ensure the Zoo logo is not a link in browser app', async ({ page }) => {
|
|||||||
test('if you write kcl with lint errors you get lints', async ({ page }) => {
|
test('if you write kcl with lint errors you get lints', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
@ -733,7 +712,6 @@ test('if you fixup kcl errors you clear lints', async ({ page }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
@ -744,19 +722,18 @@ test('if you fixup kcl errors you clear lints', async ({ page }) => {
|
|||||||
|
|
||||||
await page.getByText(' |> line([2.48, 2.44], %)').click()
|
await page.getByText(' |> line([2.48, 2.44], %)').click()
|
||||||
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error').first()).not.toBeVisible()
|
||||||
await page.keyboard.press('End')
|
await page.keyboard.press('End')
|
||||||
await page.keyboard.press('Backspace')
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||||
await page.keyboard.type(')')
|
await page.keyboard.type(')')
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error').first()).not.toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
@ -845,19 +822,8 @@ fn squareHole = (l, w) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
|
||||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
|
||||||
// but that doesn't seem to make it to the console for macos/safari :(
|
|
||||||
if (message.text().includes('start kcl lsp')) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await lspStartPromise
|
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
@ -926,7 +892,6 @@ angle: 90
|
|||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
@ -959,7 +924,7 @@ test('executes on load', async ({ page }) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// expand variables section
|
// expand variables section
|
||||||
@ -988,7 +953,7 @@ test('re-executes', async ({ page }) => {
|
|||||||
localStorage.setItem('persistCode', `const myVar = 5`)
|
localStorage.setItem('persistCode', `const myVar = 5`)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
const variablesTabButton = page.getByRole('tab', {
|
const variablesTabButton = page.getByRole('tab', {
|
||||||
@ -1020,7 +985,7 @@ const sketchOnPlaneAndBackSideTest = async (
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -1128,18 +1093,8 @@ const example = extrude(5, exampleSketch)
|
|||||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
|
||||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
|
||||||
// but that doesn't seem to make it to the console for macos/safari :(
|
|
||||||
if (message.text().includes('start kcl lsp')) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await lspStartPromise
|
|
||||||
|
|
||||||
// error in guter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
@ -1163,22 +1118,13 @@ shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
|||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Auto complete works', async ({ page }) => {
|
test.describe('Autocomplete works', () => {
|
||||||
|
test('with enter/click to accept the completion', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
|
||||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
|
||||||
// but that doesn't seem to make it to the console for macos/safari :(
|
|
||||||
if (message.text().includes('start kcl lsp')) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await lspStartPromise
|
|
||||||
|
|
||||||
// this test might be brittle as we add and remove functions
|
// this test might be brittle as we add and remove functions
|
||||||
// but should also be easy to update.
|
// but should also be easy to update.
|
||||||
@ -1190,6 +1136,7 @@ test('Auto complete works', async ({ page }) => {
|
|||||||
|
|
||||||
// expect there to be six auto complete options
|
// expect there to be six auto complete options
|
||||||
await expect(page.locator('.cm-completionLabel')).toHaveCount(6)
|
await expect(page.locator('.cm-completionLabel')).toHaveCount(6)
|
||||||
|
// this makes sure we can accept a completion with click
|
||||||
await page.getByText('startSketchOn').click()
|
await page.getByText('startSketchOn').click()
|
||||||
await page.keyboard.type("'XZ'")
|
await page.keyboard.type("'XZ'")
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
@ -1201,33 +1148,37 @@ test('Auto complete works', async ({ page }) => {
|
|||||||
await page.keyboard.press('Enter') // accepting the auto complete, not a new line
|
await page.keyboard.press('Enter') // accepting the auto complete, not a new line
|
||||||
|
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.type('12')
|
await page.keyboard.type('12')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.type(' |> lin')
|
await page.keyboard.type(' |> lin')
|
||||||
|
|
||||||
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible()
|
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
// press arrow down twice then enter to accept xLine
|
// press arrow down twice then enter to accept xLine
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.waitForTimeout(100)
|
|
||||||
// finish line with comment
|
// finish line with comment
|
||||||
await page.keyboard.type('5')
|
await page.keyboard.type('5')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.press('Tab')
|
await page.keyboard.press('Tab')
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.keyboard.type(' // lin')
|
await page.keyboard.type(' // ')
|
||||||
await page.waitForTimeout(100)
|
// Since we need to parse the ast to know we are in a comment we gotta hang tight.
|
||||||
|
await page.waitForTimeout(700)
|
||||||
|
await page.keyboard.type('lin ')
|
||||||
|
await page.waitForTimeout(200)
|
||||||
// there shouldn't be any auto complete options for 'lin' in the comment
|
// there shouldn't be any auto complete options for 'lin' in the comment
|
||||||
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
||||||
|
|
||||||
@ -1235,6 +1186,78 @@ test('Auto complete works', async ({ page }) => {
|
|||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([3.14, 12], %)
|
|> startProfileAt([3.14, 12], %)
|
||||||
|> xLine(5, %) // lin`)
|
|> xLine(5, %) // lin`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with tab to accept the completion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// this test might be brittle as we add and remove functions
|
||||||
|
// but should also be easy to update.
|
||||||
|
// tests clicking on an option, selection the first option
|
||||||
|
// and arrowing down to an option
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type('const sketch001 = startSketchO')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// Make sure just hitting tab will take the only one left
|
||||||
|
await expect(page.locator('.cm-completionLabel')).toHaveCount(1)
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.type("'XZ'")
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type(' |> startProfi')
|
||||||
|
// expect there be a single auto complete option that we can just hit enter on
|
||||||
|
await expect(page.locator('.cm-completionLabel')).toBeVisible()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab') // accepting the auto complete, not a new line
|
||||||
|
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.keyboard.type('12')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.type(' |> lin')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// press arrow down twice then tab to accept xLine
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
// finish line with comment
|
||||||
|
await page.keyboard.type('5')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
|
||||||
|
await page.keyboard.type(' // ')
|
||||||
|
// Since we need to parse the ast to know we are in a comment we gotta hang tight.
|
||||||
|
await page.waitForTimeout(700)
|
||||||
|
await page.keyboard.type('lin ')
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// there shouldn't be any auto complete options for 'lin' in the comment
|
||||||
|
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([3.14, 12], %)
|
||||||
|
|> xLine(5, %) // lin`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Stored settings are validated and fall back to defaults', async ({
|
test('Stored settings are validated and fall back to defaults', async ({
|
||||||
@ -1255,7 +1278,7 @@ test('Stored settings are validated and fall back to defaults', async ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// Check the settings were reset
|
// Check the settings were reset
|
||||||
@ -1278,8 +1301,9 @@ test('Stored settings are validated and fall back to defaults', async ({
|
|||||||
test('Project settings can be set and override user settings', async ({
|
test('Project settings can be set and override user settings', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
await u.waitForAuthSkipAppStart()
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
.waitFor({ state: 'visible' })
|
.waitFor({ state: 'visible' })
|
||||||
@ -1326,8 +1350,9 @@ test('Project settings can be set and override user settings', async ({
|
|||||||
test('Project settings can be opened with keybinding from the editor', async ({
|
test('Project settings can be opened with keybinding from the editor', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
await u.waitForAuthSkipAppStart()
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
.waitFor({ state: 'visible' })
|
.waitFor({ state: 'visible' })
|
||||||
@ -1375,8 +1400,9 @@ test('Project settings can be opened with keybinding from the editor', async ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Project and user settings can be reset', async ({ page }) => {
|
test('Project and user settings can be reset', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
await u.waitForAuthSkipAppStart()
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
.waitFor({ state: 'visible' })
|
.waitFor({ state: 'visible' })
|
||||||
@ -1450,8 +1476,10 @@ test('Project and user settings can be reset', async ({ page }) => {
|
|||||||
test('Keyboard shortcuts can be viewed through the help menu', async ({
|
test('Keyboard shortcuts can be viewed through the help menu', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -1485,7 +1513,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
// Test that the onboarding pane loaded
|
||||||
@ -1512,7 +1540,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 1080 })
|
await page.setViewportSize({ width: 1200, height: 1080 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
// Test that the onboarding pane loaded
|
||||||
@ -1551,7 +1579,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// Test that the redirect happened
|
// Test that the redirect happened
|
||||||
@ -1603,7 +1631,8 @@ test.describe('Onboarding tests', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 1080 })
|
await page.setViewportSize({ width: 1200, height: 1080 })
|
||||||
await page.goto('/')
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.waitForURL('**' + onboardingPaths.PARAMETRIC_MODELING, {
|
await page.waitForURL('**' + onboardingPaths.PARAMETRIC_MODELING, {
|
||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
})
|
})
|
||||||
@ -1647,8 +1676,10 @@ test.describe('Onboarding tests', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 1080 })
|
const u = await getUtils(page)
|
||||||
await page.goto('/')
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
// Test that the text in this step is correct
|
// Test that the text in this step is correct
|
||||||
@ -1703,7 +1734,7 @@ test.describe('Testing selections', () => {
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -2163,7 +2194,7 @@ const sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
|||||||
)
|
)
|
||||||
}, KCL_DEFAULT_LENGTH)
|
}, KCL_DEFAULT_LENGTH)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -2250,7 +2281,7 @@ const extrude001 = extrude(10, sketch001)
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -2329,7 +2360,7 @@ const part001 = startSketchOn('XZ')
|
|||||||
)
|
)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
@ -2379,7 +2410,7 @@ const extrude001 = extrude(50, sketch001)
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
@ -2463,9 +2494,9 @@ const extrude001 = extrude(50, sketch001)
|
|||||||
|
|
||||||
test.describe('Command bar tests', () => {
|
test.describe('Command bar tests', () => {
|
||||||
test('Command bar works and can change a setting', async ({ page }) => {
|
test('Command bar works and can change a setting', async ({ page }) => {
|
||||||
// Brief boilerplate
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -2524,9 +2555,9 @@ test.describe('Command bar tests', () => {
|
|||||||
test('Command bar keybinding works from code editor and can change a setting', async ({
|
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
// Brief boilerplate
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -2591,7 +2622,7 @@ test.describe('Command bar tests', () => {
|
|||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// Make sure the stream is up
|
// Make sure the stream is up
|
||||||
@ -2672,7 +2703,7 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const viewportSize = { width: 1200, height: 500 }
|
const viewportSize = { width: 1200, height: 500 }
|
||||||
await page.setViewportSize(viewportSize)
|
await page.setViewportSize(viewportSize)
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -2778,7 +2809,7 @@ test('ProgramMemory can be serialised', async ({ page }) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
const messages: string[] = []
|
const messages: string[] = []
|
||||||
|
|
||||||
// Listen for all console events and push the message text to an array
|
// Listen for all console events and push the message text to an array
|
||||||
@ -2855,7 +2886,7 @@ fn yohey = (pos) => {
|
|||||||
selectionsSnippets
|
selectionsSnippets
|
||||||
)
|
)
|
||||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -2912,7 +2943,7 @@ test('Deselecting line tool should mean nothing happens on click', async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -3033,7 +3064,7 @@ const part002 = startSketchOn('-XZ')
|
|||||||
selectionsSnippets
|
selectionsSnippets
|
||||||
)
|
)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -3069,7 +3100,7 @@ async function doEditSegmentsByDraggingHandle(page: Page, openPanes: string[]) {
|
|||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -3235,7 +3266,7 @@ test('Can edit a sketch that has been extruded in the same pipe', async ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -3336,7 +3367,7 @@ test('Can edit a sketch that has been revolved in the same pipe', async ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -3427,7 +3458,7 @@ const doSnapAtDifferentScales = async (
|
|||||||
) => {
|
) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -3542,7 +3573,7 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -3677,7 +3708,7 @@ test('Can code mod a line length', async ({ page }) => {
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -3737,7 +3768,7 @@ test('Extrude from command bar selects extrude line after', async ({
|
|||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -3781,7 +3812,7 @@ const part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %, $seg01)').click()
|
await page.getByText('line([74.36, 130.4], %, $seg01)').click()
|
||||||
@ -3841,7 +3872,7 @@ const part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %, $seg01)').click()
|
await page.getByText('line([74.36, 130.4], %, $seg01)').click()
|
||||||
@ -3940,7 +3971,7 @@ const part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
@ -4048,7 +4079,7 @@ const part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
@ -4155,7 +4186,7 @@ const part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
@ -4265,7 +4296,7 @@ const part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
@ -4341,7 +4372,7 @@ const part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
@ -4437,7 +4468,7 @@ const part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
@ -4514,7 +4545,7 @@ const part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
@ -4561,7 +4592,7 @@ const part002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await page.getByText('line([3.79, 2.68], %, $seg01)').click()
|
await page.getByText('line([3.79, 2.68], %, $seg01)').click()
|
||||||
@ -4817,7 +4848,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -4976,7 +5007,7 @@ const part001 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -5056,7 +5087,7 @@ const part001 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -5184,7 +5215,7 @@ const part001 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -5340,7 +5371,7 @@ const part001 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -5453,7 +5484,7 @@ const part001 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -5679,7 +5710,7 @@ ${extraLine ? 'const myVar = segLen(seg01, part001)' : ''}`
|
|||||||
)
|
)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
@ -5837,7 +5868,7 @@ ${extraLine ? 'const myVar = segLen(seg01, part001)' : ''}`
|
|||||||
)
|
)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
@ -5892,7 +5923,7 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
|||||||
// Wait for the app to be ready for use
|
// Wait for the app to be ready for use
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
@ -5973,7 +6004,7 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
|||||||
// Wait for the app to be ready for use
|
// Wait for the app to be ready for use
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
@ -6062,7 +6093,7 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
|||||||
test('simulate network down and network little widget', async ({ page }) => {
|
test('simulate network down and network little widget', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// This is how we wait until the stream is online
|
// This is how we wait until the stream is online
|
||||||
@ -6133,7 +6164,7 @@ test('Engine disconnect & reconnect in sketch mode', async ({ page }) => {
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -6310,7 +6341,7 @@ test.describe('Testing Gizmo', () => {
|
|||||||
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
||||||
}, TEST_CODE_GIZMO)
|
}, TEST_CODE_GIZMO)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -6400,7 +6431,7 @@ test.describe('Testing Gizmo', () => {
|
|||||||
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
||||||
}, TEST_CODE_GIZMO)
|
}, TEST_CODE_GIZMO)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -6534,7 +6565,7 @@ const part001 = startSketchOn('-XZ')
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
@ -6566,8 +6597,9 @@ test('Paste should not work unless an input is focused', async ({
|
|||||||
browserName !== 'firefox',
|
browserName !== 'firefox',
|
||||||
"This bug is really Firefox-only, which we don't run in CI."
|
"This bug is really Firefox-only, which we don't run in CI."
|
||||||
)
|
)
|
||||||
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
await u.waitForAuthSkipAppStart()
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
.waitFor({ state: 'visible' })
|
.waitFor({ state: 'visible' })
|
||||||
|
@ -91,8 +91,9 @@ const part001 = startSketchOn('-XZ')
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.waitForCmdReceive('extrude')
|
await u.waitForCmdReceive('extrude')
|
||||||
@ -330,7 +331,7 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|
|||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
@ -386,8 +387,8 @@ test('Draft segments should look right', async ({ page, context }) => {
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
@ -443,7 +444,7 @@ test('Draft rectangles should look right', async ({ page, context }) => {
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -490,7 +491,7 @@ test.describe('Client side scene scale should match engine scale', () => {
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -589,7 +590,7 @@ test.describe('Client side scene scale should match engine scale', () => {
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
@ -689,7 +690,7 @@ const part002 = startSketchOn(part001, 'seg01')
|
|||||||
}, KCL_DEFAULT_LENGTH)
|
}, KCL_DEFAULT_LENGTH)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -739,7 +740,7 @@ test('Zoom to fit on load - solid 2d', async ({ page, context }) => {
|
|||||||
}, KCL_DEFAULT_LENGTH)
|
}, KCL_DEFAULT_LENGTH)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -776,7 +777,7 @@ test('Zoom to fit on load - solid 3d', async ({ page, context }) => {
|
|||||||
}, KCL_DEFAULT_LENGTH)
|
}, KCL_DEFAULT_LENGTH)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
@ -207,6 +207,23 @@ export const getMovementUtils = (opts: any) => {
|
|||||||
return { toSU, click00r }
|
return { toSU, click00r }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForAuthAndLsp(page: Page) {
|
||||||
|
const waitForLspPromise = page.waitForEvent('console', async (message) => {
|
||||||
|
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||||
|
// but that doesn't seem to make it to the console for macos/safari :(
|
||||||
|
if (message.text().includes('start kcl lsp')) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.goto('/')
|
||||||
|
await waitForPageLoad(page)
|
||||||
|
|
||||||
|
return waitForLspPromise
|
||||||
|
}
|
||||||
|
|
||||||
export async function getUtils(page: Page) {
|
export async function getUtils(page: Page) {
|
||||||
// Chrome devtools protocol session only works in Chromium
|
// Chrome devtools protocol session only works in Chromium
|
||||||
const browserType = page.context().browser()?.browserType().name()
|
const browserType = page.context().browser()?.browserType().name()
|
||||||
@ -214,7 +231,7 @@ export async function getUtils(page: Page) {
|
|||||||
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
|
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
waitForAuthSkipAppStart: () => waitForPageLoad(page),
|
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
||||||
removeCurrentCode: () => removeCurrentCode(page),
|
removeCurrentCode: () => removeCurrentCode(page),
|
||||||
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
||||||
updateCamPosition: async (xyz: [number, number, number]) => {
|
updateCamPosition: async (xyz: [number, number, number]) => {
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
// comment
|
|
||||||
|
|
||||||
const hi = 5 + 4
|
|
43
package.json
@ -3,7 +3,12 @@
|
|||||||
"version": "0.22.6",
|
"version": "0.22.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.16.0",
|
"@codemirror/autocomplete": "^6.16.3",
|
||||||
|
"@codemirror/commands": "^6.6.0",
|
||||||
|
"@codemirror/language": "^6.10.2",
|
||||||
|
"@codemirror/lint": "^6.8.1",
|
||||||
|
"@codemirror/search": "^6.5.6",
|
||||||
|
"@codemirror/state": "^6.4.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
@ -11,8 +16,6 @@
|
|||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^0.0.67",
|
"@kittycad/lib": "^0.0.67",
|
||||||
"@lezer/javascript": "^1.4.9",
|
|
||||||
"@open-rpc/client-js": "^1.8.1",
|
|
||||||
"@react-hook/resize-observer": "^2.0.1",
|
"@react-hook/resize-observer": "^2.0.1",
|
||||||
"@replit/codemirror-interact": "^6.3.1",
|
"@replit/codemirror-interact": "^6.3.1",
|
||||||
"@tauri-apps/api": "2.0.0-beta.12",
|
"@tauri-apps/api": "2.0.0-beta.12",
|
||||||
@ -23,28 +26,17 @@
|
|||||||
"@tauri-apps/plugin-process": "^2.0.0-beta.2",
|
"@tauri-apps/plugin-process": "^2.0.0-beta.2",
|
||||||
"@tauri-apps/plugin-shell": "^2.0.0-beta.2",
|
"@tauri-apps/plugin-shell": "^2.0.0-beta.2",
|
||||||
"@tauri-apps/plugin-updater": "^2.0.0-beta.3",
|
"@tauri-apps/plugin-updater": "^2.0.0-beta.3",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
|
||||||
"@testing-library/react": "^15.0.2",
|
|
||||||
"@testing-library/user-event": "^14.5.2",
|
|
||||||
"@ts-stack/markdown": "^1.5.0",
|
"@ts-stack/markdown": "^1.5.0",
|
||||||
"@tweenjs/tween.js": "^23.1.1",
|
"@tweenjs/tween.js": "^23.1.1",
|
||||||
"@types/node": "^18.19.31",
|
|
||||||
"@types/react": "^18.3.2",
|
|
||||||
"@types/react-dom": "^18.2.25",
|
|
||||||
"@uiw/react-codemirror": "^4.21.25",
|
"@uiw/react-codemirror": "^4.21.25",
|
||||||
"@xstate/inspect": "^0.8.0",
|
"@xstate/inspect": "^0.8.0",
|
||||||
"@xstate/react": "^3.2.2",
|
"@xstate/react": "^3.2.2",
|
||||||
"crypto-js": "^4.2.0",
|
"codemirror": "^6.0.1",
|
||||||
"debounce-promise": "^3.1.2",
|
|
||||||
"decamelize": "^6.0.0",
|
"decamelize": "^6.0.0",
|
||||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
|
||||||
"formik": "^2.4.6",
|
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"html2canvas-pro": "^1.4.3",
|
"html2canvas-pro": "^1.5.0",
|
||||||
"http-server": "^14.1.1",
|
|
||||||
"json-rpc-2.0": "^1.6.0",
|
"json-rpc-2.0": "^1.6.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"node-fetch": "^3.3.2",
|
|
||||||
"re-resizable": "^6.9.11",
|
"re-resizable": "^6.9.11",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@ -55,18 +47,14 @@
|
|||||||
"react-modal-promise": "^1.0.2",
|
"react-modal-promise": "^1.0.2",
|
||||||
"react-router-dom": "^6.23.1",
|
"react-router-dom": "^6.23.1",
|
||||||
"sketch-helpers": "^0.0.4",
|
"sketch-helpers": "^0.0.4",
|
||||||
"swr": "^2.2.5",
|
|
||||||
"three": "^0.164.1",
|
"three": "^0.164.1",
|
||||||
"ts-node": "^10.9.2",
|
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"vitest": "^1.6.0",
|
|
||||||
"vscode-jsonrpc": "^8.2.1",
|
"vscode-jsonrpc": "^8.2.1",
|
||||||
"vscode-languageserver-protocol": "^3.17.5",
|
"vscode-languageserver-protocol": "^3.17.5",
|
||||||
"wasm-pack": "^0.12.1",
|
"vscode-uri": "^3.0.8",
|
||||||
"web-vitals": "^3.5.2",
|
"web-vitals": "^3.5.2",
|
||||||
"ws": "^8.17.0",
|
|
||||||
"xstate": "^4.38.2",
|
"xstate": "^4.38.2",
|
||||||
"zustand": "^4.5.2"
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
@ -123,11 +111,14 @@
|
|||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@tauri-apps/cli": "^2.0.0-beta.13",
|
"@tauri-apps/cli": "^2.0.0-beta.13",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@types/debounce-promise": "^3.1.9",
|
"@testing-library/react": "^15.0.2",
|
||||||
"@types/mocha": "^10.0.6",
|
"@types/mocha": "^10.0.6",
|
||||||
|
"@types/node": "^18.19.31",
|
||||||
"@types/pixelmatch": "^5.2.6",
|
"@types/pixelmatch": "^5.2.6",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
|
"@types/react": "^18.3.2",
|
||||||
|
"@types/react-dom": "^18.2.25",
|
||||||
"@types/react-modal": "^3.16.3",
|
"@types/react-modal": "^3.16.3",
|
||||||
"@types/three": "^0.163.0",
|
"@types/three": "^0.163.0",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
@ -147,8 +138,11 @@
|
|||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-css-modules": "^2.12.0",
|
"eslint-plugin-css-modules": "^2.12.0",
|
||||||
|
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||||
"happy-dom": "^14.3.10",
|
"happy-dom": "^14.3.10",
|
||||||
|
"http-server": "^14.1.1",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
|
"node-fetch": "^3.3.2",
|
||||||
"pixelmatch": "^5.3.0",
|
"pixelmatch": "^5.3.0",
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
@ -160,8 +154,11 @@
|
|||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-package-version": "^1.1.0",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
|
"vitest": "^1.6.0",
|
||||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||||
"wait-on": "^7.2.0",
|
"wait-on": "^7.2.0",
|
||||||
|
"wasm-pack": "^0.12.1",
|
||||||
|
"ws": "^8.17.0",
|
||||||
"yarn": "^1.22.22"
|
"yarn": "^1.22.22"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
src-tauri/Cargo.lock
generated
@ -1212,7 +1212,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
version = "0.1.18"
|
version = "0.1.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"convert_case 0.6.0",
|
"convert_case 0.6.0",
|
||||||
@ -2576,7 +2576,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.67"
|
version = "0.1.68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
@ -6029,9 +6029,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ts-rs"
|
name = "ts-rs"
|
||||||
version = "9.0.0"
|
version = "9.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e2dcf58e612adda9a83800731e8e4aba04d8a302b9029617b0b6e4b021d5357"
|
checksum = "b44017f9f875786e543595076374b9ef7d13465a518dd93d6ccdbf5b432dde8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -6043,9 +6043,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ts-rs-macros"
|
name = "ts-rs-macros"
|
||||||
version = "9.0.0"
|
version = "9.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cbdee324e50a7402416d9c25270d3df4241ed528af5d36dda18b6f219551c577"
|
checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -568,6 +568,7 @@ export class SceneEntities {
|
|||||||
|
|
||||||
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
|
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
|
||||||
sceneInfra.resetMouseListeners()
|
sceneInfra.resetMouseListeners()
|
||||||
|
|
||||||
const { truncatedAst, programMemoryOverride, sketchGroup } =
|
const { truncatedAst, programMemoryOverride, sketchGroup } =
|
||||||
await this.setupSketch({
|
await this.setupSketch({
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
|
@ -10,7 +10,7 @@ import React, {
|
|||||||
import { FromServer, IntoServer } from 'editor/plugins/lsp/codec'
|
import { FromServer, IntoServer } from 'editor/plugins/lsp/codec'
|
||||||
import Client from '../editor/plugins/lsp/client'
|
import Client from '../editor/plugins/lsp/client'
|
||||||
import { TEST, VITE_KC_API_BASE_URL } from 'env'
|
import { TEST, VITE_KC_API_BASE_URL } from 'env'
|
||||||
import kclLanguage from 'editor/plugins/lsp/kcl/language'
|
import KclLanguageSupport from 'editor/plugins/lsp/kcl/language'
|
||||||
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
|
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
@ -31,6 +31,8 @@ import { PROJECT_ENTRYPOINT } from 'lib/constants'
|
|||||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
import { codeManager } from 'lib/singletons'
|
||||||
|
|
||||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||||
return []
|
return []
|
||||||
@ -128,17 +130,31 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const fromServer: FromServer | Error = FromServer.create()
|
const fromServer: FromServer | Error = FromServer.create()
|
||||||
if (err(fromServer)) return { lspClient: null }
|
if (err(fromServer)) return { lspClient: null }
|
||||||
|
|
||||||
const client = new Client(fromServer, intoServer)
|
const client = new Client(fromServer, intoServer, () => {
|
||||||
|
|
||||||
setIsLspReady(true)
|
setIsLspReady(true)
|
||||||
|
})
|
||||||
|
|
||||||
const lspClient = new LanguageServerClient({ client, name: LspWorker.Kcl })
|
const lspClient = new LanguageServerClient({ client, name: LspWorker.Kcl })
|
||||||
|
|
||||||
return { lspClient }
|
return { lspClient }
|
||||||
}, [
|
}, [
|
||||||
// We need a token for authenticating the server.
|
// We need a token for authenticating the server.
|
||||||
token,
|
token,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
useMemo(() => {
|
||||||
|
if (!isTauri() && isKclLspServerReady && kclLspClient && codeManager.code) {
|
||||||
|
kclLspClient.textDocumentDidOpen({
|
||||||
|
textDocument: {
|
||||||
|
uri: `file:///${PROJECT_ENTRYPOINT}`,
|
||||||
|
languageId: 'kcl',
|
||||||
|
version: 1,
|
||||||
|
text: codeManager.code,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [kclLspClient, isKclLspServerReady])
|
||||||
|
|
||||||
// Here we initialize the plugin which will start the client.
|
// Here we initialize the plugin which will start the client.
|
||||||
// Now that we have multi-file support the name of the file is a dep of
|
// Now that we have multi-file support the name of the file is a dep of
|
||||||
// this use memo, as well as the directory structure, which I think is
|
// this use memo, as well as the directory structure, which I think is
|
||||||
@ -148,7 +164,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
let plugin = null
|
let plugin = null
|
||||||
if (isKclLspServerReady && !TEST && kclLspClient) {
|
if (isKclLspServerReady && !TEST && kclLspClient) {
|
||||||
// Set up the lsp plugin.
|
// Set up the lsp plugin.
|
||||||
const lsp = kclLanguage({
|
const lsp = new KclLanguageSupport({
|
||||||
documentUri: `file:///${PROJECT_ENTRYPOINT}`,
|
documentUri: `file:///${PROJECT_ENTRYPOINT}`,
|
||||||
workspaceFolders: getWorkspaceFolders(),
|
workspaceFolders: getWorkspaceFolders(),
|
||||||
client: kclLspClient,
|
client: kclLspClient,
|
||||||
@ -205,9 +221,9 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const fromServer: FromServer | Error = FromServer.create()
|
const fromServer: FromServer | Error = FromServer.create()
|
||||||
if (err(fromServer)) return { lspClient: null }
|
if (err(fromServer)) return { lspClient: null }
|
||||||
|
|
||||||
const client = new Client(fromServer, intoServer)
|
const client = new Client(fromServer, intoServer, () => {
|
||||||
|
|
||||||
setIsCopilotReady(true)
|
setIsCopilotReady(true)
|
||||||
|
})
|
||||||
|
|
||||||
const lspClient = new LanguageServerClient({
|
const lspClient = new LanguageServerClient({
|
||||||
client,
|
client,
|
||||||
|
@ -71,7 +71,7 @@ import { TEST } from 'env'
|
|||||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { EditorSelection } from '@uiw/react-codemirror'
|
import { EditorSelection, Transaction } from '@uiw/react-codemirror'
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||||
@ -80,6 +80,7 @@ import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
|||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
|
import { modelingMachineEvent } from 'editor/manager'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -281,11 +282,15 @@ export const ModelingMachineProvider = ({
|
|||||||
const dispatchSelection = (selection?: EditorSelection) => {
|
const dispatchSelection = (selection?: EditorSelection) => {
|
||||||
if (!selection) return // TODO less of hack for the below please
|
if (!selection) return // TODO less of hack for the below please
|
||||||
if (!editorManager.editorView) return
|
if (!editorManager.editorView) return
|
||||||
editorManager.lastSelectionEvent = Date.now()
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (editorManager.editorView) {
|
if (!editorManager.editorView) return
|
||||||
editorManager.editorView.dispatch({ selection })
|
editorManager.editorView.dispatch({
|
||||||
}
|
selection,
|
||||||
|
annotations: [
|
||||||
|
modelingMachineEvent,
|
||||||
|
Transaction.addToHistory.of(false),
|
||||||
|
],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
let selections: Selections = {
|
let selections: Selections = {
|
||||||
@ -328,11 +333,6 @@ export const ModelingMachineProvider = ({
|
|||||||
)
|
)
|
||||||
updateSceneObjectColors()
|
updateSceneObjectColors()
|
||||||
|
|
||||||
// side effect to stop code mirror from updating the same selections again
|
|
||||||
editorManager.lastSelection = selections.codeBasedSelections
|
|
||||||
.map(({ range }) => `${range[1]}->${range[1]}`)
|
|
||||||
.join('&')
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectionRanges: selections,
|
selectionRanges: selections,
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,10 @@ export const KclEditorPane = () => {
|
|||||||
|
|
||||||
const textWrapping = context.textEditor.textWrapping
|
const textWrapping = context.textEditor.textWrapping
|
||||||
const cursorBlinking = context.textEditor.blinkingCursor
|
const cursorBlinking = context.textEditor.blinkingCursor
|
||||||
|
// DO NOT ADD THE CODEMIRROR HOTKEYS HERE TO THE DEPENDENCY ARRAY
|
||||||
|
// It reloads the editor every time we do _anything_ in the editor
|
||||||
|
// I have no idea why.
|
||||||
|
// Instead, hot load hotkeys via code mirror native.
|
||||||
const codeMirrorHotkeys = codeManager.getCodemirrorHotkeys()
|
const codeMirrorHotkeys = codeManager.getCodemirrorHotkeys()
|
||||||
|
|
||||||
const editorExtensions = useMemo(() => {
|
const editorExtensions = useMemo(() => {
|
||||||
@ -134,7 +138,6 @@ export const KclEditorPane = () => {
|
|||||||
highlightSelectionMatches(),
|
highlightSelectionMatches(),
|
||||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||||
rectangularSelection(),
|
rectangularSelection(),
|
||||||
drawSelection(),
|
|
||||||
dropCursor(),
|
dropCursor(),
|
||||||
interact({
|
interact({
|
||||||
rules: [
|
rules: [
|
||||||
@ -173,13 +176,7 @@ export const KclEditorPane = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return extensions
|
return extensions
|
||||||
}, [
|
}, [kclLSP, copilotLSP, textWrapping.current, cursorBlinking.current])
|
||||||
kclLSP,
|
|
||||||
copilotLSP,
|
|
||||||
textWrapping.current,
|
|
||||||
cursorBlinking.current,
|
|
||||||
codeMirrorHotkeys,
|
|
||||||
])
|
|
||||||
|
|
||||||
const initialCode = useRef(codeManager.code)
|
const initialCode = useRef(codeManager.code)
|
||||||
|
|
||||||
@ -192,9 +189,9 @@ export const KclEditorPane = () => {
|
|||||||
value={initialCode.current}
|
value={initialCode.current}
|
||||||
extensions={editorExtensions}
|
extensions={editorExtensions}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
onCreateEditor={(_editorView) =>
|
onCreateEditor={(_editorView) => {
|
||||||
editorManager.setEditorView(_editorView)
|
editorManager.setEditorView(_editorView)
|
||||||
}
|
}}
|
||||||
indentWithTab={false}
|
indentWithTab={false}
|
||||||
basicSetup={false}
|
basicSetup={false}
|
||||||
/>
|
/>
|
||||||
|
@ -1,13 +1,25 @@
|
|||||||
import { hasNextSnippetField } from '@codemirror/autocomplete'
|
|
||||||
import { EditorView, ViewUpdate } from '@codemirror/view'
|
import { EditorView, ViewUpdate } from '@codemirror/view'
|
||||||
import { EditorSelection, SelectionRange } from '@codemirror/state'
|
import { EditorSelection, Annotation, Transaction } from '@codemirror/state'
|
||||||
import { engineCommandManager, sceneInfra } from 'lib/singletons'
|
import { engineCommandManager } from 'lib/singletons'
|
||||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||||
import { Selections, processCodeMirrorRanges, Selection } from 'lib/selections'
|
import { Selections, processCodeMirrorRanges, Selection } from 'lib/selections'
|
||||||
import { undo, redo } from '@codemirror/commands'
|
import { undo, redo } from '@codemirror/commands'
|
||||||
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
|
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
|
||||||
import { addLineHighlight } from './highlightextension'
|
import { addLineHighlight } from './highlightextension'
|
||||||
import { forEachDiagnostic, setDiagnostics, Diagnostic } from '@codemirror/lint'
|
import {
|
||||||
|
forEachDiagnostic,
|
||||||
|
Diagnostic,
|
||||||
|
setDiagnosticsEffect,
|
||||||
|
} from '@codemirror/lint'
|
||||||
|
|
||||||
|
const updateOutsideEditorAnnotation = Annotation.define<null>()
|
||||||
|
export const updateOutsideEditorEvent = updateOutsideEditorAnnotation.of(null)
|
||||||
|
|
||||||
|
const modelingMachineAnnotation = Annotation.define<null>()
|
||||||
|
export const modelingMachineEvent = modelingMachineAnnotation.of(null)
|
||||||
|
|
||||||
|
const setDiagnosticsAnnotation = Annotation.define<null>()
|
||||||
|
export const setDiagnosticsEvent = setDiagnosticsAnnotation.of(null)
|
||||||
|
|
||||||
function diagnosticIsEqual(d1: Diagnostic, d2: Diagnostic): boolean {
|
function diagnosticIsEqual(d1: Diagnostic, d2: Diagnostic): boolean {
|
||||||
return d1.from === d2.from && d1.to === d2.to && d1.message === d2.message
|
return d1.from === d2.from && d1.to === d2.to && d1.message === d2.message
|
||||||
@ -22,8 +34,6 @@ export default class EditorManager {
|
|||||||
codeBasedSelections: [],
|
codeBasedSelections: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
private _lastSelectionEvent: number | null = null
|
|
||||||
lastSelection: string = ''
|
|
||||||
private _lastEvent: { event: string; time: number } | null = null
|
private _lastEvent: { event: string; time: number } | null = null
|
||||||
|
|
||||||
private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
|
private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
|
||||||
@ -57,10 +67,6 @@ export default class EditorManager {
|
|||||||
this._selectionRanges = selectionRanges
|
this._selectionRanges = selectionRanges
|
||||||
}
|
}
|
||||||
|
|
||||||
set lastSelectionEvent(time: number) {
|
|
||||||
this._lastSelectionEvent = time
|
|
||||||
}
|
|
||||||
|
|
||||||
set modelingSend(send: (eventInfo: ModelingMachineEvent) => void) {
|
set modelingSend(send: (eventInfo: ModelingMachineEvent) => void) {
|
||||||
this._modelingSend = send
|
this._modelingSend = send
|
||||||
}
|
}
|
||||||
@ -83,32 +89,38 @@ export default class EditorManager {
|
|||||||
|
|
||||||
setHighlightRange(selection: Selection['range']): void {
|
setHighlightRange(selection: Selection['range']): void {
|
||||||
this._highlightRange = selection
|
this._highlightRange = selection
|
||||||
const editorView = this.editorView
|
|
||||||
const safeEnd = Math.min(
|
const safeEnd = Math.min(
|
||||||
selection[1],
|
selection[1],
|
||||||
editorView?.state.doc.length || selection[1]
|
this._editorView?.state.doc.length || selection[1]
|
||||||
)
|
)
|
||||||
if (editorView) {
|
if (this._editorView) {
|
||||||
editorView.dispatch({
|
this._editorView.dispatch({
|
||||||
effects: addLineHighlight.of([selection[0], safeEnd]),
|
effects: addLineHighlight.of([selection[0], safeEnd]),
|
||||||
|
annotations: [
|
||||||
|
updateOutsideEditorEvent,
|
||||||
|
Transaction.addToHistory.of(false),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearDiagnostics(): void {
|
clearDiagnostics(): void {
|
||||||
if (!this.editorView) return
|
this.setDiagnostics([])
|
||||||
this.editorView.dispatch(setDiagnostics(this.editorView.state, []))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setDiagnostics(diagnostics: Diagnostic[]): void {
|
setDiagnostics(diagnostics: Diagnostic[]): void {
|
||||||
if (!this.editorView) return
|
if (!this._editorView) return
|
||||||
this.editorView.dispatch(setDiagnostics(this.editorView.state, diagnostics))
|
|
||||||
|
this._editorView.dispatch({
|
||||||
|
effects: [setDiagnosticsEffect.of(diagnostics)],
|
||||||
|
annotations: [setDiagnosticsEvent, Transaction.addToHistory.of(false)],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addDiagnostics(diagnostics: Diagnostic[]): void {
|
addDiagnostics(diagnostics: Diagnostic[]): void {
|
||||||
if (!this.editorView) return
|
if (!this._editorView) return
|
||||||
|
|
||||||
forEachDiagnostic(this.editorView.state, function (diag) {
|
forEachDiagnostic(this._editorView.state, function (diag) {
|
||||||
diagnostics.push(diag)
|
diagnostics.push(diag)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -122,9 +134,7 @@ export default class EditorManager {
|
|||||||
uniqueDiagnostics.add(diagnostic)
|
uniqueDiagnostics.add(diagnostic)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.editorView.dispatch(
|
this.setDiagnostics([...uniqueDiagnostics])
|
||||||
setDiagnostics(this.editorView.state, [...uniqueDiagnostics])
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
@ -174,50 +184,33 @@ export default class EditorManager {
|
|||||||
].range[1]
|
].range[1]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (!this.editorView) {
|
|
||||||
|
if (!this._editorView) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.editorView.dispatch({
|
|
||||||
|
this._editorView.dispatch({
|
||||||
selection: EditorSelection.create(codeBasedSelections, 1),
|
selection: EditorSelection.create(codeBasedSelections, 1),
|
||||||
|
annotations: [
|
||||||
|
updateOutsideEditorEvent,
|
||||||
|
Transaction.addToHistory.of(false),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We will ONLY get here if the user called a select event.
|
||||||
|
// This is handled by the code mirror kcl plugin.
|
||||||
|
// If you call this function from somewhere else, you best know wtf you are
|
||||||
|
// doing. (jess)
|
||||||
handleOnViewUpdate(viewUpdate: ViewUpdate): void {
|
handleOnViewUpdate(viewUpdate: ViewUpdate): void {
|
||||||
// If we are just fucking around in a snippet, return early and don't
|
if (!this._editorView) {
|
||||||
// trigger stuff below that might cause the component to re-render.
|
|
||||||
// Otherwise we will not be able to tab thru the snippet portions.
|
|
||||||
// We explicitly dont check HasPrevSnippetField because we always add
|
|
||||||
// a ${} to the end of the function so that's fine.
|
|
||||||
if (hasNextSnippetField(viewUpdate.view.state)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.editorView === null) {
|
|
||||||
this.setEditorView(viewUpdate.view)
|
this.setEditorView(viewUpdate.view)
|
||||||
}
|
}
|
||||||
const selString = stringifyRanges(
|
|
||||||
viewUpdate?.state?.selection?.ranges || []
|
|
||||||
)
|
|
||||||
|
|
||||||
if (selString === this.lastSelection) {
|
const ranges = viewUpdate?.state?.selection?.ranges || []
|
||||||
// onUpdate is noisy and is fired a lot by extensions
|
if (ranges.length === 0) {
|
||||||
// since we're only interested in selections changes we can ignore most of these.
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// note this is also set from the "Set selection" action to stop code mirror from updating selections right after
|
|
||||||
// selections are made from the scene
|
|
||||||
this.lastSelection = selString
|
|
||||||
|
|
||||||
if (
|
|
||||||
this._lastSelectionEvent &&
|
|
||||||
Date.now() - this._lastSelectionEvent < 150
|
|
||||||
) {
|
|
||||||
return // update triggered by scene selection
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sceneInfra.selected) {
|
|
||||||
return // mid drag
|
|
||||||
}
|
|
||||||
|
|
||||||
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
||||||
'Equip Line tool',
|
'Equip Line tool',
|
||||||
@ -266,7 +259,3 @@ export default class EditorManager {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifyRanges(ranges: readonly SelectionRange[]): string {
|
|
||||||
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
|
|
||||||
}
|
|
||||||
|
@ -67,8 +67,13 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
|||||||
#fromServer: FromServer
|
#fromServer: FromServer
|
||||||
private serverCapabilities: LSP.ServerCapabilities<any> = {}
|
private serverCapabilities: LSP.ServerCapabilities<any> = {}
|
||||||
private notifyFn: ((message: LSP.NotificationMessage) => void) | null = null
|
private notifyFn: ((message: LSP.NotificationMessage) => void) | null = null
|
||||||
|
private initializedCallback: () => void
|
||||||
|
|
||||||
constructor(fromServer: FromServer, intoServer: IntoServer) {
|
constructor(
|
||||||
|
fromServer: FromServer,
|
||||||
|
intoServer: IntoServer,
|
||||||
|
initializedCallback: () => void
|
||||||
|
) {
|
||||||
super(
|
super(
|
||||||
new jsrpc.JSONRPCServer(),
|
new jsrpc.JSONRPCServer(),
|
||||||
new jsrpc.JSONRPCClient(async (json: jsrpc.JSONRPCRequest) => {
|
new jsrpc.JSONRPCClient(async (json: jsrpc.JSONRPCRequest) => {
|
||||||
@ -82,6 +87,7 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
this.#fromServer = fromServer
|
this.#fromServer = fromServer
|
||||||
|
this.initializedCallback = initializedCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
@ -163,6 +169,8 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
|||||||
// notify "initialized": client --> server
|
// notify "initialized": client --> server
|
||||||
this.notify(LSP.InitializedNotification.type.method, {})
|
this.notify(LSP.InitializedNotification.type.method, {})
|
||||||
|
|
||||||
|
this.initializedCallback()
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.afterInitializedHooks.map((f: () => Promise<void>) => f())
|
this.afterInitializedHooks.map((f: () => Promise<void>) => f())
|
||||||
)
|
)
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
Decoration,
|
Decoration,
|
||||||
DecorationSet,
|
DecorationSet,
|
||||||
EditorView,
|
EditorView,
|
||||||
|
PluginValue,
|
||||||
ViewPlugin,
|
ViewPlugin,
|
||||||
ViewUpdate,
|
ViewUpdate,
|
||||||
} from '@codemirror/view'
|
} from '@codemirror/view'
|
||||||
@ -11,7 +12,6 @@ import {
|
|||||||
Annotation,
|
Annotation,
|
||||||
EditorState,
|
EditorState,
|
||||||
Extension,
|
Extension,
|
||||||
Prec,
|
|
||||||
StateEffect,
|
StateEffect,
|
||||||
StateField,
|
StateField,
|
||||||
Transaction,
|
Transaction,
|
||||||
@ -19,15 +19,30 @@ import {
|
|||||||
import { completionStatus } from '@codemirror/autocomplete'
|
import { completionStatus } from '@codemirror/autocomplete'
|
||||||
import { offsetToPos, posToOffset } from 'editor/plugins/lsp/util'
|
import { offsetToPos, posToOffset } from 'editor/plugins/lsp/util'
|
||||||
import { LanguageServerOptions, LanguageServerClient } from 'editor/plugins/lsp'
|
import { LanguageServerOptions, LanguageServerClient } from 'editor/plugins/lsp'
|
||||||
|
import { deferExecution } from 'lib/utils'
|
||||||
import {
|
import {
|
||||||
LanguageServerPlugin,
|
LanguageServerPlugin,
|
||||||
documentUri,
|
TransactionAnnotation,
|
||||||
|
docPathFacet,
|
||||||
languageId,
|
languageId,
|
||||||
|
updateInfo,
|
||||||
workspaceFolders,
|
workspaceFolders,
|
||||||
|
RelevantUpdate,
|
||||||
} from 'editor/plugins/lsp/plugin'
|
} from 'editor/plugins/lsp/plugin'
|
||||||
|
|
||||||
|
const copilotPluginAnnotation = Annotation.define<null>()
|
||||||
|
export const copilotPluginEvent = copilotPluginAnnotation.of(null)
|
||||||
|
|
||||||
|
// Effects to tell StateEffect what to do with GhostText
|
||||||
|
const addSuggestion = StateEffect.define<Suggestion>()
|
||||||
|
const acceptSuggestion = StateEffect.define<null>()
|
||||||
|
const clearSuggestion = StateEffect.define<null>()
|
||||||
|
const typeFirst = StateEffect.define<number>()
|
||||||
|
|
||||||
const ghostMark = Decoration.mark({ class: 'cm-ghostText' })
|
const ghostMark = Decoration.mark({ class: 'cm-ghostText' })
|
||||||
|
|
||||||
|
const changesDelay = 600
|
||||||
|
|
||||||
interface Suggestion {
|
interface Suggestion {
|
||||||
text: string
|
text: string
|
||||||
displayText: string
|
displayText: string
|
||||||
@ -38,15 +53,10 @@ interface Suggestion {
|
|||||||
uuid: string
|
uuid: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Effects to tell StateEffect what to do with GhostText
|
|
||||||
const addSuggestion = StateEffect.define<Suggestion>()
|
|
||||||
const acceptSuggestion = StateEffect.define<null>()
|
|
||||||
const clearSuggestion = StateEffect.define<null>()
|
|
||||||
const typeFirst = StateEffect.define<number>()
|
|
||||||
|
|
||||||
interface CompletionState {
|
interface CompletionState {
|
||||||
ghostText: GhostText | null
|
ghostText: GhostText | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GhostText {
|
interface GhostText {
|
||||||
text: string
|
text: string
|
||||||
displayText: string
|
displayText: string
|
||||||
@ -65,6 +75,16 @@ export const completionDecoration = StateField.define<CompletionState>({
|
|||||||
return { ghostText: null }
|
return { ghostText: null }
|
||||||
},
|
},
|
||||||
update(state: CompletionState, transaction: Transaction) {
|
update(state: CompletionState, transaction: Transaction) {
|
||||||
|
// We only care about events from this plugin.
|
||||||
|
if (transaction.annotation(copilotPluginEvent.type) === undefined) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only care about transactions with effects.
|
||||||
|
if (!transaction.effects) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
for (const effect of transaction.effects) {
|
for (const effect of transaction.effects) {
|
||||||
if (effect.is(addSuggestion)) {
|
if (effect.is(addSuggestion)) {
|
||||||
// When adding a suggestion, we set th ghostText
|
// When adding a suggestion, we set th ghostText
|
||||||
@ -160,216 +180,152 @@ export const completionDecoration = StateField.define<CompletionState>({
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
const copilotEvent = Annotation.define<null>()
|
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||||
|
const infos = updateInfo(update)
|
||||||
|
|
||||||
/****************************************************************************
|
// Make sure we are not in a snippet
|
||||||
************************* COMMANDS ******************************************
|
if (infos.some((info) => info.inSnippet)) {
|
||||||
*****************************************************************************/
|
return {
|
||||||
|
overall: false,
|
||||||
const acceptSuggestionCommand = (
|
userSelect: false,
|
||||||
copilotClient: LanguageServerClient,
|
time: null,
|
||||||
view: EditorView
|
}
|
||||||
) => {
|
|
||||||
// We delete the ghost text and insert the suggestion.
|
|
||||||
// We also set the cursor to the end of the suggestion.
|
|
||||||
const ghostText = view.state.field(completionDecoration)!.ghostText
|
|
||||||
if (!ghostText) {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ghostTextStart = ghostText.displayPos
|
return {
|
||||||
const ghostTextEnd = ghostText.endGhostText
|
overall: infos.some(
|
||||||
|
(info) =>
|
||||||
const actualTextStart = ghostText.startPos
|
update.focusChanged ||
|
||||||
const actualTextEnd = ghostText.endPos
|
info.annotations.includes(TransactionAnnotation.UserSelect) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||||
const replacementEnd = ghostText.endReplacement
|
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||||
const suggestion = ghostText.text
|
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserMove) ||
|
||||||
view.dispatch({
|
info.annotations.includes(TransactionAnnotation.Copoilot)
|
||||||
changes: {
|
),
|
||||||
from: ghostTextStart,
|
userSelect: infos.some((info) =>
|
||||||
to: ghostTextEnd,
|
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||||
insert: '',
|
),
|
||||||
},
|
time: infos.length ? infos[0].time : null,
|
||||||
// selection: {anchor: actualTextEnd},
|
|
||||||
effects: acceptSuggestion.of(null),
|
|
||||||
annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)],
|
|
||||||
})
|
|
||||||
|
|
||||||
const tmpTextEnd = replacementEnd - (ghostTextEnd - ghostTextStart)
|
|
||||||
|
|
||||||
view.dispatch({
|
|
||||||
changes: {
|
|
||||||
from: actualTextStart,
|
|
||||||
to: tmpTextEnd,
|
|
||||||
insert: suggestion,
|
|
||||||
},
|
|
||||||
selection: { anchor: actualTextEnd },
|
|
||||||
annotations: [copilotEvent.of(null), Transaction.addToHistory.of(true)],
|
|
||||||
})
|
|
||||||
|
|
||||||
copilotClient.accept(ghostText.uuid)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
export const rejectSuggestionCommand = (
|
|
||||||
copilotClient: LanguageServerClient,
|
|
||||||
view: EditorView
|
|
||||||
) => {
|
|
||||||
// We delete the suggestion, then carry through with the original keypress
|
|
||||||
const ghostText = view.state.field(completionDecoration)!.ghostText
|
|
||||||
if (!ghostText) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const ghostTextStart = ghostText.displayPos
|
|
||||||
const ghostTextEnd = ghostText.endGhostText
|
|
||||||
|
|
||||||
view.dispatch({
|
|
||||||
changes: {
|
|
||||||
from: ghostTextStart,
|
|
||||||
to: ghostTextEnd,
|
|
||||||
insert: '',
|
|
||||||
},
|
|
||||||
effects: clearSuggestion.of(null),
|
|
||||||
annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)],
|
|
||||||
})
|
|
||||||
|
|
||||||
copilotClient.reject()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const sameKeyCommand = (
|
|
||||||
copilotClient: LanguageServerClient,
|
|
||||||
view: EditorView,
|
|
||||||
key: string
|
|
||||||
) => {
|
|
||||||
// When we type a key that is the same as the first letter of the suggestion, we delete the first letter of the suggestion and carry through with the original keypress
|
|
||||||
const ghostText = view.state.field(completionDecoration)!.ghostText
|
|
||||||
if (!ghostText) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const ghostTextStart = ghostText.displayPos
|
|
||||||
const indent = view.state.facet(indentUnit)
|
|
||||||
|
|
||||||
if (key === 'Tab' && ghostText.displayText.startsWith(indent)) {
|
|
||||||
view.dispatch({
|
|
||||||
selection: { anchor: ghostTextStart + indent.length },
|
|
||||||
effects: typeFirst.of(indent.length),
|
|
||||||
annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)],
|
|
||||||
})
|
|
||||||
return true
|
|
||||||
} else if (key === 'Tab') {
|
|
||||||
return acceptSuggestionCommand(copilotClient, view)
|
|
||||||
} else if (ghostText.weirdInsert || key !== ghostText.displayText[0]) {
|
|
||||||
return rejectSuggestionCommand(copilotClient, view)
|
|
||||||
} else if (ghostText.displayText.length === 1) {
|
|
||||||
return acceptSuggestionCommand(copilotClient, view)
|
|
||||||
} else {
|
|
||||||
// Use this to delete the first letter of the suggestion
|
|
||||||
view.dispatch({
|
|
||||||
selection: { anchor: ghostTextStart + 1 },
|
|
||||||
effects: typeFirst.of(1),
|
|
||||||
annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)],
|
|
||||||
})
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const completionPlugin = (copilotClient: LanguageServerClient) =>
|
|
||||||
EditorView.domEventHandlers({
|
|
||||||
keydown(event, view) {
|
|
||||||
if (
|
|
||||||
event.key !== 'Shift' &&
|
|
||||||
event.key !== 'Control' &&
|
|
||||||
event.key !== 'Alt' &&
|
|
||||||
event.key !== 'Meta'
|
|
||||||
) {
|
|
||||||
return sameKeyCommand(copilotClient, view, event.key)
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mousedown(event, view) {
|
|
||||||
return rejectSuggestionCommand(copilotClient, view)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const viewCompletionPlugin = (copilotClient: LanguageServerClient) =>
|
|
||||||
EditorView.updateListener.of((update) => {
|
|
||||||
if (update.focusChanged) {
|
|
||||||
rejectSuggestionCommand(copilotClient, update.view)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// A view plugin that requests completions from the server after a delay
|
// A view plugin that requests completions from the server after a delay
|
||||||
const completionRequester = (client: LanguageServerClient) => {
|
export class CompletionRequester implements PluginValue {
|
||||||
let timeout: any = null
|
private client: LanguageServerClient
|
||||||
let lastPos = 0
|
private lastPos: number = 0
|
||||||
|
private viewUpdate: ViewUpdate | null = null
|
||||||
|
|
||||||
const badUpdate = (update: ViewUpdate) => {
|
private _deffererCodeUpdate = deferExecution(() => {
|
||||||
for (const tr of update.transactions) {
|
if (this.viewUpdate === null) {
|
||||||
if (tr.annotation(copilotEvent) !== undefined) {
|
return
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.requestCompletions()
|
||||||
|
}, changesDelay)
|
||||||
|
|
||||||
|
private _deffererUserSelect = deferExecution(() => {
|
||||||
|
if (this.viewUpdate === null) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.rejectSuggestionCommand()
|
||||||
|
}, changesDelay)
|
||||||
|
|
||||||
|
constructor(client: LanguageServerClient) {
|
||||||
|
this.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
update(viewUpdate: ViewUpdate) {
|
||||||
|
this.viewUpdate = viewUpdate
|
||||||
|
|
||||||
|
const isRelevant = relevantUpdate(viewUpdate)
|
||||||
|
if (!isRelevant.overall) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a user select event, we want to clear the ghost text.
|
||||||
|
if (isRelevant.userSelect) {
|
||||||
|
this._deffererUserSelect(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewUpdate.focusChanged) {
|
||||||
|
this.rejectSuggestionCommand()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!viewUpdate.docChanged) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastPos = this.viewUpdate.state.selection.main.head
|
||||||
|
this._deffererCodeUpdate(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
ghostText(): GhostText | null {
|
||||||
|
if (!this.viewUpdate) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.viewUpdate.view.state.field(completionDecoration)?.ghostText || null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
containsGhostText(): boolean {
|
||||||
|
return this.ghostText() !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
autocompleting(): boolean {
|
||||||
|
if (!this.viewUpdate) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const containsGhostText = (update: ViewUpdate) => {
|
|
||||||
return update.state.field(completionDecoration).ghostText != null
|
return completionStatus(this.viewUpdate.state) === 'active'
|
||||||
}
|
|
||||||
const autocompleting = (update: ViewUpdate) => {
|
|
||||||
return completionStatus(update.state) === 'active'
|
|
||||||
}
|
|
||||||
const notFocused = (update: ViewUpdate) => {
|
|
||||||
return !update.view.hasFocus
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return EditorView.updateListener.of((update: ViewUpdate) => {
|
notFocused(): boolean {
|
||||||
if (
|
if (!this.viewUpdate) {
|
||||||
update.docChanged &&
|
return true
|
||||||
!update.transactions.some((tr) =>
|
|
||||||
tr.effects.some((e) => e.is(acceptSuggestion) || e.is(clearSuggestion))
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// Cancel the previous timeout
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return !this.viewUpdate.view.hasFocus
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestCompletions(): Promise<void> {
|
||||||
if (
|
if (
|
||||||
badUpdate(update) ||
|
this.viewUpdate === null ||
|
||||||
containsGhostText(update) ||
|
this.containsGhostText() ||
|
||||||
autocompleting(update) ||
|
this.autocompleting() ||
|
||||||
notFocused(update)
|
this.notFocused() ||
|
||||||
|
!this.viewUpdate.docChanged
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current position and source
|
const pos = this.viewUpdate.state.selection.main.head
|
||||||
const state = update.state
|
|
||||||
const pos = state.selection.main.head
|
|
||||||
const source = state.doc.toString()
|
|
||||||
|
|
||||||
const dUri = state.facet(documentUri)
|
|
||||||
const path = dUri.split('/').pop()!
|
|
||||||
const relativePath = dUri.replace('file://', '')
|
|
||||||
|
|
||||||
// Set a new timeout to request completion
|
|
||||||
timeout = setTimeout(async () => {
|
|
||||||
// Check if the position has changed
|
// Check if the position has changed
|
||||||
if (pos === lastPos) {
|
if (pos !== this.lastPos) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current position and source
|
||||||
|
const state = this.viewUpdate.state
|
||||||
|
const dUri = state.facet(docPathFacet)
|
||||||
|
|
||||||
// Request completion from the server
|
// Request completion from the server
|
||||||
try {
|
const completionResult = await this.client.getCompletion({
|
||||||
const completionResult = await client.getCompletion({
|
|
||||||
doc: {
|
doc: {
|
||||||
source,
|
source: state.doc.toString(),
|
||||||
tabSize: state.facet(EditorState.tabSize),
|
tabSize: state.facet(EditorState.tabSize),
|
||||||
indentSize: 1,
|
indentSize: 1,
|
||||||
insertSpaces: true,
|
insertSpaces: true,
|
||||||
path,
|
path: dUri.split('/').pop()!,
|
||||||
uri: dUri,
|
uri: dUri,
|
||||||
relativePath,
|
relativePath: dUri.replace('file://', ''),
|
||||||
languageId: state.facet(languageId),
|
languageId: state.facet(languageId),
|
||||||
position: offsetToPos(state.doc, pos),
|
position: offsetToPos(state.doc, pos),
|
||||||
},
|
},
|
||||||
@ -387,40 +343,52 @@ const completionRequester = (client: LanguageServerClient) => {
|
|||||||
uuid,
|
uuid,
|
||||||
} = completionResult.completions[0]
|
} = completionResult.completions[0]
|
||||||
|
|
||||||
|
if (text.length === 0 || displayText.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const startPos = posToOffset(state.doc, {
|
const startPos = posToOffset(state.doc, {
|
||||||
line: start.line,
|
line: start.line,
|
||||||
character: start.character,
|
character: start.character,
|
||||||
})!
|
})
|
||||||
|
|
||||||
const endGhostPos =
|
if (startPos === undefined) {
|
||||||
posToOffset(state.doc, {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const endGhostOffset = posToOffset(state.doc, {
|
||||||
line: position.line,
|
line: position.line,
|
||||||
character: position.character,
|
character: position.character,
|
||||||
})! + displayText.length
|
})
|
||||||
|
if (endGhostOffset === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const endGhostPos = endGhostOffset + displayText.length
|
||||||
// EndPos is the position that marks the complete end
|
// EndPos is the position that marks the complete end
|
||||||
// of what is to be replaced when we accept a completion
|
// of what is to be replaced when we accept a completion
|
||||||
// result
|
// result
|
||||||
const endPos = startPos + text.length
|
const endPos = startPos + text.length
|
||||||
|
|
||||||
// Check if the position is still the same
|
// Check if they changed position.
|
||||||
if (
|
if (pos !== this.lastPos) {
|
||||||
pos === lastPos &&
|
return
|
||||||
completionStatus(update.view.state) !== 'active' &&
|
}
|
||||||
update.view.hasFocus
|
|
||||||
) {
|
// Make sure we are not currently completing.
|
||||||
|
if (this.autocompleting() || this.notFocused()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Dispatch an effect to add the suggestion
|
// Dispatch an effect to add the suggestion
|
||||||
// If the completion starts before the end of the line, check the end of the line with the end of the completion
|
// If the completion starts before the end of the line, check the end of the line with the end of the completion.
|
||||||
const line = update.view.state.doc.lineAt(pos)
|
const line = this.viewUpdate.view.state.doc.lineAt(pos)
|
||||||
if (line.to !== pos) {
|
if (line.to !== pos) {
|
||||||
const ending = update.view.state.doc.sliceString(pos, line.to)
|
const ending = this.viewUpdate.view.state.doc.sliceString(pos, line.to)
|
||||||
if (displayText.endsWith(ending)) {
|
if (displayText.endsWith(ending)) {
|
||||||
displayText = displayText.slice(
|
displayText = displayText.slice(0, displayText.length - ending.length)
|
||||||
0,
|
|
||||||
displayText.length - ending.length
|
|
||||||
)
|
|
||||||
} else if (displayText.includes(ending)) {
|
} else if (displayText.includes(ending)) {
|
||||||
// Remove the ending
|
// Remove the ending
|
||||||
update.view.dispatch({
|
this.viewUpdate.view.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: pos,
|
from: pos,
|
||||||
to: line.to,
|
to: line.to,
|
||||||
@ -428,14 +396,12 @@ const completionRequester = (client: LanguageServerClient) => {
|
|||||||
},
|
},
|
||||||
selection: { anchor: pos },
|
selection: { anchor: pos },
|
||||||
effects: typeFirst.of(ending.length),
|
effects: typeFirst.of(ending.length),
|
||||||
annotations: [
|
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||||
copilotEvent.of(null),
|
|
||||||
Transaction.addToHistory.of(false),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update.view.dispatch({
|
|
||||||
|
this.viewUpdate.view.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: pos,
|
from: pos,
|
||||||
to: pos,
|
to: pos,
|
||||||
@ -452,39 +418,168 @@ const completionRequester = (client: LanguageServerClient) => {
|
|||||||
uuid,
|
uuid,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
annotations: [
|
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||||
copilotEvent.of(null),
|
|
||||||
Transaction.addToHistory.of(false),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('copilot completion failed', error)
|
|
||||||
// Javascript wait for 500ms for some reason is necessary here.
|
|
||||||
// TODO - FIGURE OUT WHY THIS RESOLVES THE BUG
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
this.lastPos = pos
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
acceptSuggestionCommand(): boolean {
|
||||||
|
if (!this.viewUpdate) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}, 150)
|
|
||||||
// Update the last position
|
const ghostText = this.ghostText()
|
||||||
lastPos = pos
|
if (!ghostText) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We delete the ghost text and insert the suggestion.
|
||||||
|
// We also set the cursor to the end of the suggestion.
|
||||||
|
const ghostTextStart = ghostText.displayPos
|
||||||
|
const ghostTextEnd = ghostText.endGhostText
|
||||||
|
|
||||||
|
const actualTextStart = ghostText.startPos
|
||||||
|
const actualTextEnd = ghostText.endPos
|
||||||
|
|
||||||
|
const replacementEnd = ghostText.endReplacement
|
||||||
|
|
||||||
|
const suggestion = ghostText.text
|
||||||
|
|
||||||
|
this.viewUpdate.view.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: ghostTextStart,
|
||||||
|
to: ghostTextEnd,
|
||||||
|
insert: '',
|
||||||
|
},
|
||||||
|
effects: acceptSuggestion.of(null),
|
||||||
|
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const tmpTextEnd = replacementEnd - (ghostTextEnd - ghostTextStart)
|
||||||
|
|
||||||
|
this.viewUpdate.view.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: actualTextStart,
|
||||||
|
to: tmpTextEnd,
|
||||||
|
insert: suggestion,
|
||||||
|
},
|
||||||
|
selection: { anchor: actualTextEnd },
|
||||||
|
annotations: [copilotPluginEvent, Transaction.addToHistory.of(true)],
|
||||||
|
})
|
||||||
|
|
||||||
|
this.client.accept(ghostText.uuid)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectSuggestionCommand(): boolean {
|
||||||
|
if (!this.viewUpdate) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const ghostText = this.ghostText()
|
||||||
|
if (!ghostText) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// We delete the suggestion, then carry through with the original keypress
|
||||||
|
const ghostTextStart = ghostText.displayPos
|
||||||
|
const ghostTextEnd = ghostText.endGhostText
|
||||||
|
|
||||||
|
this.viewUpdate.view.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: ghostTextStart,
|
||||||
|
to: ghostTextEnd,
|
||||||
|
insert: '',
|
||||||
|
},
|
||||||
|
effects: clearSuggestion.of(null),
|
||||||
|
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||||
|
})
|
||||||
|
|
||||||
|
this.client.reject()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sameKeyCommand(key: string) {
|
||||||
|
if (!this.viewUpdate) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const ghostText = this.ghostText()
|
||||||
|
if (!ghostText) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabKey = 'Tab'
|
||||||
|
|
||||||
|
// When we type a key that is the same as the first letter of the suggestion, we delete the first letter of the suggestion and carry through with the original keypress
|
||||||
|
const ghostTextStart = ghostText.displayPos
|
||||||
|
const indent = this.viewUpdate.view.state.facet(indentUnit)
|
||||||
|
|
||||||
|
if (key === tabKey && ghostText.displayText.startsWith(indent)) {
|
||||||
|
this.viewUpdate.view.dispatch({
|
||||||
|
selection: { anchor: ghostTextStart + indent.length },
|
||||||
|
effects: typeFirst.of(indent.length),
|
||||||
|
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
} else if (key === tabKey) {
|
||||||
|
return this.acceptSuggestionCommand()
|
||||||
|
} else if (ghostText.weirdInsert || key !== ghostText.displayText[0]) {
|
||||||
|
return this.rejectSuggestionCommand()
|
||||||
|
} else if (ghostText.displayText.length === 1) {
|
||||||
|
return this.acceptSuggestionCommand()
|
||||||
|
} else {
|
||||||
|
// Use this to delete the first letter of the suggestion
|
||||||
|
this.viewUpdate.view.dispatch({
|
||||||
|
selection: { anchor: ghostTextStart + 1 },
|
||||||
|
effects: typeFirst.of(1),
|
||||||
|
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||||
|
const completionPlugin = ViewPlugin.define((view) => {
|
||||||
|
return new CompletionRequester(options.client)
|
||||||
|
})
|
||||||
|
|
||||||
|
const domHandlers = EditorView.domEventHandlers({
|
||||||
|
keydown(event, view) {
|
||||||
|
if (
|
||||||
|
event.key !== 'Shift' &&
|
||||||
|
event.key !== 'Control' &&
|
||||||
|
event.key !== 'Alt' &&
|
||||||
|
event.key !== 'Meta'
|
||||||
|
) {
|
||||||
|
if (view.plugin === null) return false
|
||||||
|
|
||||||
|
// Get the current plugin from the map.
|
||||||
|
const p = view.plugin(completionPlugin)
|
||||||
|
if (p === null) return false
|
||||||
|
|
||||||
|
return p.sameKeyCommand(event.key)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return [
|
return [
|
||||||
documentUri.of(options.documentUri),
|
docPathFacet.of(options.documentUri),
|
||||||
languageId.of('kcl'),
|
languageId.of('kcl'),
|
||||||
workspaceFolders.of(options.workspaceFolders),
|
workspaceFolders.of(options.workspaceFolders),
|
||||||
ViewPlugin.define(
|
ViewPlugin.define(
|
||||||
(view) =>
|
(view) =>
|
||||||
new LanguageServerPlugin(options.client, view, options.allowHTMLContent)
|
new LanguageServerPlugin(options.client, view, options.allowHTMLContent)
|
||||||
),
|
),
|
||||||
|
completionPlugin,
|
||||||
|
domHandlers,
|
||||||
completionDecoration,
|
completionDecoration,
|
||||||
Prec.highest(completionPlugin(options.client)),
|
|
||||||
Prec.highest(viewCompletionPlugin(options.client)),
|
|
||||||
completionRequester(options.client),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
import Client from './client'
|
import Client from './client'
|
||||||
import { SemanticToken, deserializeTokens } from './kcl/semantic_tokens'
|
|
||||||
import { LanguageServerPlugin } from 'editor/plugins/lsp/plugin'
|
import { LanguageServerPlugin } from 'editor/plugins/lsp/plugin'
|
||||||
import { CopilotLspCompletionParams } from 'wasm-lib/kcl/bindings/CopilotLspCompletionParams'
|
import { CopilotLspCompletionParams } from 'wasm-lib/kcl/bindings/CopilotLspCompletionParams'
|
||||||
import { CopilotCompletionResponse } from 'wasm-lib/kcl/bindings/CopilotCompletionResponse'
|
import { CopilotCompletionResponse } from 'wasm-lib/kcl/bindings/CopilotCompletionResponse'
|
||||||
@ -68,7 +67,7 @@ export interface LanguageServerOptions {
|
|||||||
|
|
||||||
export class LanguageServerClient {
|
export class LanguageServerClient {
|
||||||
private client: Client
|
private client: Client
|
||||||
readonly name: string
|
readonly name: LspWorker
|
||||||
|
|
||||||
public ready: boolean
|
public ready: boolean
|
||||||
|
|
||||||
@ -76,8 +75,6 @@ export class LanguageServerClient {
|
|||||||
|
|
||||||
public initializePromise: Promise<void>
|
public initializePromise: Promise<void>
|
||||||
|
|
||||||
private isUpdatingSemanticTokens: boolean = false
|
|
||||||
private semanticTokens: SemanticToken[] = []
|
|
||||||
private queuedUids: string[] = []
|
private queuedUids: string[] = []
|
||||||
|
|
||||||
constructor(options: LanguageServerClientOptions) {
|
constructor(options: LanguageServerClientOptions) {
|
||||||
@ -111,19 +108,10 @@ export class LanguageServerClient {
|
|||||||
|
|
||||||
textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams) {
|
textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams) {
|
||||||
this.notify('textDocument/didOpen', params)
|
this.notify('textDocument/didOpen', params)
|
||||||
|
|
||||||
// Update the facet of the plugins to the correct value.
|
|
||||||
for (const plugin of this.plugins) {
|
|
||||||
plugin.documentUri = params.textDocument.uri
|
|
||||||
plugin.languageId = params.textDocument.languageId
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateSemanticTokens(params.textDocument.uri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textDocumentDidChange(params: LSP.DidChangeTextDocumentParams) {
|
textDocumentDidChange(params: LSP.DidChangeTextDocumentParams) {
|
||||||
this.notify('textDocument/didChange', params)
|
this.notify('textDocument/didChange', params)
|
||||||
this.updateSemanticTokens(params.textDocument.uri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textDocumentDidClose(params: LSP.DidCloseTextDocumentParams) {
|
textDocumentDidClose(params: LSP.DidCloseTextDocumentParams) {
|
||||||
@ -134,18 +122,9 @@ export class LanguageServerClient {
|
|||||||
added: LSP.WorkspaceFolder[],
|
added: LSP.WorkspaceFolder[],
|
||||||
removed: LSP.WorkspaceFolder[]
|
removed: LSP.WorkspaceFolder[]
|
||||||
) {
|
) {
|
||||||
// Add all the current workspace folders in the plugin to removed.
|
|
||||||
for (const plugin of this.plugins) {
|
|
||||||
removed.push(...plugin.workspaceFolders)
|
|
||||||
}
|
|
||||||
this.notify('workspace/didChangeWorkspaceFolders', {
|
this.notify('workspace/didChangeWorkspaceFolders', {
|
||||||
event: { added, removed },
|
event: { added, removed },
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add all the new workspace folders to the plugins.
|
|
||||||
for (const plugin of this.plugins) {
|
|
||||||
plugin.workspaceFolders = added
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaceDidCreateFiles(params: LSP.CreateFilesParams) {
|
workspaceDidCreateFiles(params: LSP.CreateFilesParams) {
|
||||||
@ -160,33 +139,13 @@ export class LanguageServerClient {
|
|||||||
this.notify('workspace/didDeleteFiles', params)
|
this.notify('workspace/didDeleteFiles', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSemanticTokens(uri: string) {
|
async textDocumentSemanticTokensFull(params: LSP.SemanticTokensParams) {
|
||||||
const serverCapabilities = this.getServerCapabilities()
|
const serverCapabilities = this.getServerCapabilities()
|
||||||
if (!serverCapabilities.semanticTokensProvider) {
|
if (!serverCapabilities.semanticTokensProvider) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we can only run, if we aren't already running.
|
return this.request('textDocument/semanticTokens/full', params)
|
||||||
if (!this.isUpdatingSemanticTokens) {
|
|
||||||
this.isUpdatingSemanticTokens = true
|
|
||||||
|
|
||||||
const result = await this.request('textDocument/semanticTokens/full', {
|
|
||||||
textDocument: {
|
|
||||||
uri,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
this.semanticTokens = await deserializeTokens(
|
|
||||||
result.data,
|
|
||||||
this.getServerCapabilities().semanticTokensProvider
|
|
||||||
)
|
|
||||||
|
|
||||||
this.isUpdatingSemanticTokens = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSemanticTokens(): SemanticToken[] {
|
|
||||||
return this.semanticTokens
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async textDocumentHover(params: LSP.HoverParams) {
|
async textDocumentHover(params: LSP.HoverParams) {
|
||||||
|
@ -1,4 +1,14 @@
|
|||||||
import { autocompletion } from '@codemirror/autocomplete'
|
import {
|
||||||
|
acceptCompletion,
|
||||||
|
autocompletion,
|
||||||
|
clearSnippet,
|
||||||
|
closeCompletion,
|
||||||
|
hasNextSnippetField,
|
||||||
|
moveCompletionSelection,
|
||||||
|
nextSnippetField,
|
||||||
|
prevSnippetField,
|
||||||
|
startCompletion,
|
||||||
|
} from '@codemirror/autocomplete'
|
||||||
import { Extension, EditorState, Prec } from '@codemirror/state'
|
import { Extension, EditorState, Prec } from '@codemirror/state'
|
||||||
import {
|
import {
|
||||||
ViewPlugin,
|
ViewPlugin,
|
||||||
@ -7,6 +17,8 @@ import {
|
|||||||
keymap,
|
keymap,
|
||||||
KeyBinding,
|
KeyBinding,
|
||||||
tooltips,
|
tooltips,
|
||||||
|
PluginValue,
|
||||||
|
ViewUpdate,
|
||||||
} from '@codemirror/view'
|
} from '@codemirror/view'
|
||||||
import { CompletionTriggerKind } from 'vscode-languageserver-protocol'
|
import { CompletionTriggerKind } from 'vscode-languageserver-protocol'
|
||||||
import { offsetToPos } from 'editor/plugins/lsp/util'
|
import { offsetToPos } from 'editor/plugins/lsp/util'
|
||||||
@ -14,11 +26,18 @@ import { LanguageServerOptions } from 'editor/plugins/lsp'
|
|||||||
import { syntaxTree, indentService, foldService } from '@codemirror/language'
|
import { syntaxTree, indentService, foldService } from '@codemirror/language'
|
||||||
import { linter, forEachDiagnostic, Diagnostic } from '@codemirror/lint'
|
import { linter, forEachDiagnostic, Diagnostic } from '@codemirror/lint'
|
||||||
import {
|
import {
|
||||||
|
docPathFacet,
|
||||||
LanguageServerPlugin,
|
LanguageServerPlugin,
|
||||||
documentUri,
|
|
||||||
languageId,
|
languageId,
|
||||||
workspaceFolders,
|
workspaceFolders,
|
||||||
|
updateInfo,
|
||||||
|
RelevantUpdate,
|
||||||
|
TransactionAnnotation,
|
||||||
} from 'editor/plugins/lsp/plugin'
|
} from 'editor/plugins/lsp/plugin'
|
||||||
|
import { deferExecution } from 'lib/utils'
|
||||||
|
import { codeManager, editorManager, kclManager } from 'lib/singletons'
|
||||||
|
|
||||||
|
const changesDelay = 600
|
||||||
|
|
||||||
export const kclIndentService = () => {
|
export const kclIndentService = () => {
|
||||||
// Match the indentation of the previous line (if present).
|
// Match the indentation of the previous line (if present).
|
||||||
@ -39,6 +58,81 @@ export const kclIndentService = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||||
|
const infos = updateInfo(update)
|
||||||
|
// Make sure we are not in a snippet
|
||||||
|
if (infos.some((info) => info.inSnippet)) {
|
||||||
|
return {
|
||||||
|
overall: false,
|
||||||
|
userSelect: false,
|
||||||
|
time: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
overall: infos.some(
|
||||||
|
(info) =>
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserSelect) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserMove)
|
||||||
|
),
|
||||||
|
userSelect: infos.some((info) =>
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||||
|
),
|
||||||
|
time: infos.length ? infos[0].time : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A view plugin that requests completions from the server after a delay
|
||||||
|
export class KclPlugin implements PluginValue {
|
||||||
|
private viewUpdate: ViewUpdate | null = null
|
||||||
|
|
||||||
|
private _deffererCodeUpdate = deferExecution(() => {
|
||||||
|
if (this.viewUpdate === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kclManager.executeCode()
|
||||||
|
}, changesDelay)
|
||||||
|
|
||||||
|
private _deffererUserSelect = deferExecution(() => {
|
||||||
|
if (this.viewUpdate === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
editorManager.handleOnViewUpdate(this.viewUpdate)
|
||||||
|
}, 50)
|
||||||
|
|
||||||
|
update(viewUpdate: ViewUpdate) {
|
||||||
|
this.viewUpdate = viewUpdate
|
||||||
|
editorManager.setEditorView(viewUpdate.view)
|
||||||
|
|
||||||
|
const isRelevant = relevantUpdate(viewUpdate)
|
||||||
|
if (!isRelevant.overall) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a user select event, we want to update what parts are
|
||||||
|
// highlighted.
|
||||||
|
if (isRelevant.userSelect) {
|
||||||
|
this._deffererUserSelect(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!viewUpdate.docChanged) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCode = viewUpdate.state.doc.toString()
|
||||||
|
codeManager.code = newCode
|
||||||
|
codeManager.writeToFile()
|
||||||
|
|
||||||
|
this._deffererCodeUpdate(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function kclPlugin(options: LanguageServerOptions): Extension {
|
export function kclPlugin(options: LanguageServerOptions): Extension {
|
||||||
let plugin: LanguageServerPlugin | null = null
|
let plugin: LanguageServerPlugin | null = null
|
||||||
const viewPlugin = ViewPlugin.define(
|
const viewPlugin = ViewPlugin.define(
|
||||||
@ -58,8 +152,8 @@ export function kclPlugin(options: LanguageServerOptions): Extension {
|
|||||||
|
|
||||||
// Get the current plugin from the map.
|
// Get the current plugin from the map.
|
||||||
const p = view.plugin(viewPlugin)
|
const p = view.plugin(viewPlugin)
|
||||||
|
|
||||||
if (p === null) return false
|
if (p === null) return false
|
||||||
|
|
||||||
p.requestFormatting()
|
p.requestFormatting()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
@ -68,6 +162,39 @@ export function kclPlugin(options: LanguageServerOptions): Extension {
|
|||||||
// Create an extension for the key mappings.
|
// Create an extension for the key mappings.
|
||||||
const kclKeymapExt = Prec.highest(keymap.computeN([], () => [kclKeymap]))
|
const kclKeymapExt = Prec.highest(keymap.computeN([], () => [kclKeymap]))
|
||||||
|
|
||||||
|
const autocompleteKeymap: readonly KeyBinding[] = [
|
||||||
|
{ key: 'Ctrl-Space', run: startCompletion },
|
||||||
|
{
|
||||||
|
key: 'Escape',
|
||||||
|
run: (view: EditorView): boolean => {
|
||||||
|
if (clearSnippet(view)) return true
|
||||||
|
|
||||||
|
return closeCompletion(view)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ key: 'ArrowDown', run: moveCompletionSelection(true) },
|
||||||
|
{ key: 'ArrowUp', run: moveCompletionSelection(false) },
|
||||||
|
{ key: 'PageDown', run: moveCompletionSelection(true, 'page') },
|
||||||
|
{ key: 'PageUp', run: moveCompletionSelection(false, 'page') },
|
||||||
|
{ key: 'Enter', run: acceptCompletion },
|
||||||
|
{
|
||||||
|
key: 'Tab',
|
||||||
|
run: (view: EditorView): boolean => {
|
||||||
|
if (hasNextSnippetField(view.state)) {
|
||||||
|
const result = nextSnippetField(view)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return acceptCompletion(view)
|
||||||
|
},
|
||||||
|
shift: prevSnippetField,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const autocompleteKeymapExt = Prec.highest(
|
||||||
|
keymap.computeN([], () => [autocompleteKeymap])
|
||||||
|
)
|
||||||
|
|
||||||
const folding = foldService.of(
|
const folding = foldService.of(
|
||||||
(state: EditorState, lineStart: number, lineEnd: number) => {
|
(state: EditorState, lineStart: number, lineEnd: number) => {
|
||||||
if (plugin == null) return null
|
if (plugin == null) return null
|
||||||
@ -79,10 +206,11 @@ export function kclPlugin(options: LanguageServerOptions): Extension {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
documentUri.of(options.documentUri),
|
docPathFacet.of(options.documentUri),
|
||||||
languageId.of('kcl'),
|
languageId.of('kcl'),
|
||||||
workspaceFolders.of(options.workspaceFolders),
|
workspaceFolders.of(options.workspaceFolders),
|
||||||
viewPlugin,
|
viewPlugin,
|
||||||
|
ViewPlugin.define((view) => new KclPlugin()),
|
||||||
kclKeymapExt,
|
kclKeymapExt,
|
||||||
kclIndentService(),
|
kclIndentService(),
|
||||||
hoverTooltip(
|
hoverTooltip(
|
||||||
@ -104,8 +232,9 @@ export function kclPlugin(options: LanguageServerOptions): Extension {
|
|||||||
return diagnostics
|
return diagnostics
|
||||||
}),
|
}),
|
||||||
folding,
|
folding,
|
||||||
|
autocompleteKeymapExt,
|
||||||
autocompletion({
|
autocompletion({
|
||||||
defaultKeymap: true,
|
defaultKeymap: false,
|
||||||
override: [
|
override: [
|
||||||
async (context) => {
|
async (context) => {
|
||||||
if (plugin == null) return null
|
if (plugin == null) return null
|
||||||
|
@ -8,10 +8,19 @@ import {
|
|||||||
import { LanguageServerClient } from 'editor/plugins/lsp'
|
import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||||
import { kclPlugin } from '.'
|
import { kclPlugin } from '.'
|
||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
import { parser as jsParser } from '@lezer/javascript'
|
import KclParser from './parser'
|
||||||
import { EditorState } from '@uiw/react-codemirror'
|
import { semanticTokenField } from '../plugin'
|
||||||
|
|
||||||
const data = defineLanguageFacet({})
|
const data = defineLanguageFacet({
|
||||||
|
// https://codemirror.net/docs/ref/#commands.CommentTokens
|
||||||
|
commentTokens: {
|
||||||
|
line: '//',
|
||||||
|
block: {
|
||||||
|
open: '/*',
|
||||||
|
close: '*/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export interface LanguageOptions {
|
export interface LanguageOptions {
|
||||||
workspaceFolders: LSP.WorkspaceFolder[]
|
workspaceFolders: LSP.WorkspaceFolder[]
|
||||||
@ -28,34 +37,24 @@ class KclLanguage extends Language {
|
|||||||
client: options.client,
|
client: options.client,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const parser = new KclParser()
|
||||||
|
|
||||||
super(
|
super(
|
||||||
data,
|
data,
|
||||||
// For now let's use the javascript parser.
|
// For now let's use the javascript parser.
|
||||||
// It works really well and has good syntax highlighting.
|
// It works really well and has good syntax highlighting.
|
||||||
// We can use our lsp for the rest.
|
// We can use our lsp for the rest.
|
||||||
jsParser,
|
parser,
|
||||||
[
|
[plugin],
|
||||||
plugin,
|
|
||||||
EditorState.languageData.of(() => [
|
|
||||||
{
|
|
||||||
// https://codemirror.net/docs/ref/#commands.CommentTokens
|
|
||||||
commentTokens: {
|
|
||||||
line: '//',
|
|
||||||
block: {
|
|
||||||
open: '/*',
|
|
||||||
close: '*/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
'kcl'
|
'kcl'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function kclLanguage(options: LanguageOptions): LanguageSupport {
|
export default class KclLanguageSupport extends LanguageSupport {
|
||||||
|
constructor(options: LanguageOptions) {
|
||||||
const lang = new KclLanguage(options)
|
const lang = new KclLanguage(options)
|
||||||
|
|
||||||
return new LanguageSupport(lang)
|
super(lang, [semanticTokenField])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
// Extends the codemirror Parser for kcl.
|
// Extends the codemirror Parser for kcl.
|
||||||
|
// This is really just a no-op parser since we use semantic tokens from the LSP
|
||||||
|
// server.
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Parser,
|
Parser,
|
||||||
@ -7,91 +9,27 @@ import {
|
|||||||
PartialParse,
|
PartialParse,
|
||||||
Tree,
|
Tree,
|
||||||
NodeType,
|
NodeType,
|
||||||
NodeSet,
|
|
||||||
} from '@lezer/common'
|
} from '@lezer/common'
|
||||||
import { LanguageServerClient } from 'editor/plugins/lsp'
|
|
||||||
import { posToOffset } from 'editor/plugins/lsp/util'
|
|
||||||
import { SemanticToken } from './semantic_tokens'
|
|
||||||
import { DocInput } from '@codemirror/language'
|
import { DocInput } from '@codemirror/language'
|
||||||
import { tags, styleTags } from '@lezer/highlight'
|
|
||||||
|
|
||||||
export default class KclParser extends Parser {
|
export default class KclParser extends Parser {
|
||||||
private client: LanguageServerClient
|
|
||||||
|
|
||||||
constructor(client: LanguageServerClient) {
|
|
||||||
super()
|
|
||||||
this.client = client
|
|
||||||
}
|
|
||||||
|
|
||||||
createParse(
|
createParse(
|
||||||
input: Input,
|
input: Input,
|
||||||
fragments: readonly TreeFragment[],
|
fragments: readonly TreeFragment[],
|
||||||
ranges: readonly { from: number; to: number }[]
|
ranges: readonly { from: number; to: number }[]
|
||||||
): PartialParse {
|
): PartialParse {
|
||||||
let parse: PartialParse = new Context(this, input, fragments, ranges)
|
let parse: PartialParse = new Context(input)
|
||||||
return parse
|
return parse
|
||||||
}
|
}
|
||||||
|
|
||||||
getTokenTypes(): string[] {
|
|
||||||
return this.client.getServerCapabilities().semanticTokensProvider!.legend
|
|
||||||
.tokenTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
getSemanticTokens(): SemanticToken[] {
|
|
||||||
return this.client.getSemanticTokens()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Context implements PartialParse {
|
class Context implements PartialParse {
|
||||||
private parser: KclParser
|
|
||||||
private input: DocInput
|
private input: DocInput
|
||||||
private fragments: readonly TreeFragment[]
|
|
||||||
private ranges: readonly { from: number; to: number }[]
|
|
||||||
|
|
||||||
private nodeTypes: { [key: string]: NodeType }
|
|
||||||
stoppedAt: number = 0
|
stoppedAt: number = 0
|
||||||
|
|
||||||
private semanticTokens: SemanticToken[] = []
|
constructor(input: Input) {
|
||||||
private currentLine: number = 0
|
|
||||||
private currentColumn: number = 0
|
|
||||||
private nodeSet: NodeSet
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
/// The parser configuration used.
|
|
||||||
parser: KclParser,
|
|
||||||
input: Input,
|
|
||||||
fragments: readonly TreeFragment[],
|
|
||||||
ranges: readonly { from: number; to: number }[]
|
|
||||||
) {
|
|
||||||
this.parser = parser
|
|
||||||
this.input = input as DocInput
|
this.input = input as DocInput
|
||||||
this.fragments = fragments
|
|
||||||
this.ranges = ranges
|
|
||||||
|
|
||||||
// Iterate over the semantic token types and create a node type for each.
|
|
||||||
this.nodeTypes = {}
|
|
||||||
let nodeArray: NodeType[] = []
|
|
||||||
this.parser.getTokenTypes().forEach((tokenType, index) => {
|
|
||||||
const nodeType = NodeType.define({
|
|
||||||
id: index,
|
|
||||||
name: tokenType,
|
|
||||||
// props: [this.styleTags],
|
|
||||||
})
|
|
||||||
this.nodeTypes[tokenType] = nodeType
|
|
||||||
nodeArray.push(nodeType)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.semanticTokens = this.parser.getSemanticTokens()
|
|
||||||
const styles = styleTags({
|
|
||||||
number: tags.number,
|
|
||||||
variable: tags.variableName,
|
|
||||||
operator: tags.operator,
|
|
||||||
keyword: tags.keyword,
|
|
||||||
string: tags.string,
|
|
||||||
comment: tags.comment,
|
|
||||||
function: tags.function(tags.variableName),
|
|
||||||
})
|
|
||||||
this.nodeSet = new NodeSet(nodeArray).extend(styles)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get parsedPos(): number {
|
get parsedPos(): number {
|
||||||
@ -99,67 +37,8 @@ class Context implements PartialParse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
advance(): Tree | null {
|
advance(): Tree | null {
|
||||||
if (this.semanticTokens.length === 0) {
|
|
||||||
return new Tree(NodeType.none, [], [], 0)
|
|
||||||
}
|
|
||||||
const tree = this.createTree(this.semanticTokens[0], 0)
|
|
||||||
this.stoppedAt = this.input.doc.length
|
this.stoppedAt = this.input.doc.length
|
||||||
return tree
|
return new Tree(NodeType.none, [], [], this.input.doc.length)
|
||||||
}
|
|
||||||
|
|
||||||
createTree(token: SemanticToken, index: number): Tree {
|
|
||||||
const changedLine = token.delta_line !== 0
|
|
||||||
this.currentLine += token.delta_line
|
|
||||||
if (changedLine) {
|
|
||||||
this.currentColumn = 0
|
|
||||||
}
|
|
||||||
this.currentColumn += token.delta_start
|
|
||||||
|
|
||||||
// Let's get our position relative to the start of the file.
|
|
||||||
let currentPosition = posToOffset(this.input.doc, {
|
|
||||||
line: this.currentLine,
|
|
||||||
character: this.currentColumn,
|
|
||||||
})
|
|
||||||
|
|
||||||
const nodeType = this.nodeSet.types[this.nodeTypes[token.token_type].id]
|
|
||||||
|
|
||||||
if (currentPosition === undefined) {
|
|
||||||
// This is bad and weird.
|
|
||||||
return new Tree(nodeType, [], [], token.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= this.semanticTokens.length - 1) {
|
|
||||||
// We have no children.
|
|
||||||
return new Tree(nodeType, [], [], token.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextIndex = index + 1
|
|
||||||
const nextToken = this.semanticTokens[nextIndex]
|
|
||||||
const changedLineNext = nextToken.delta_line !== 0
|
|
||||||
const nextLine = this.currentLine + nextToken.delta_line
|
|
||||||
const nextColumn = changedLineNext
|
|
||||||
? nextToken.delta_start
|
|
||||||
: this.currentColumn + nextToken.delta_start
|
|
||||||
const nextPosition = posToOffset(this.input.doc, {
|
|
||||||
line: nextLine,
|
|
||||||
character: nextColumn,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (nextPosition === undefined) {
|
|
||||||
// This is bad and weird.
|
|
||||||
return new Tree(nodeType, [], [], token.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's get the
|
|
||||||
|
|
||||||
return new Tree(
|
|
||||||
nodeType,
|
|
||||||
[this.createTree(nextToken, nextIndex)],
|
|
||||||
|
|
||||||
// The positions (offsets relative to the start of this tree) of the children.
|
|
||||||
[nextPosition - currentPosition],
|
|
||||||
token.length
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stopAt(pos: number) {
|
stopAt(pos: number) {
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
|
||||||
|
|
||||||
export class SemanticToken {
|
|
||||||
delta_line: number
|
|
||||||
delta_start: number
|
|
||||||
length: number
|
|
||||||
token_type: string
|
|
||||||
token_modifiers_bitset: string
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
delta_line = 0,
|
|
||||||
delta_start = 0,
|
|
||||||
length = 0,
|
|
||||||
token_type = '',
|
|
||||||
token_modifiers_bitset = ''
|
|
||||||
) {
|
|
||||||
this.delta_line = delta_line
|
|
||||||
this.delta_start = delta_start
|
|
||||||
this.length = length
|
|
||||||
this.token_type = token_type
|
|
||||||
this.token_modifiers_bitset = token_modifiers_bitset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deserializeTokens(
|
|
||||||
data: number[],
|
|
||||||
semanticTokensProvider?: LSP.SemanticTokensOptions
|
|
||||||
): Promise<SemanticToken[]> {
|
|
||||||
if (!semanticTokensProvider) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
// Check if data length is divisible by 5
|
|
||||||
if (data.length % 5 !== 0) {
|
|
||||||
return Promise.reject(new Error('Length is not divisible by 5'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const tokens = []
|
|
||||||
for (let i = 0; i < data.length; i += 5) {
|
|
||||||
tokens.push(
|
|
||||||
new SemanticToken(
|
|
||||||
data[i],
|
|
||||||
data[i + 1],
|
|
||||||
data[i + 2],
|
|
||||||
semanticTokensProvider.legend.tokenTypes[data[i + 3]],
|
|
||||||
semanticTokensProvider.legend.tokenModifiers[data[i + 4]]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokens
|
|
||||||
}
|
|
@ -1,7 +1,24 @@
|
|||||||
import { completeFromList, snippetCompletion } from '@codemirror/autocomplete'
|
import {
|
||||||
import { setDiagnostics } from '@codemirror/lint'
|
completeFromList,
|
||||||
import { Facet } from '@codemirror/state'
|
hasNextSnippetField,
|
||||||
import { EditorView, Tooltip } from '@codemirror/view'
|
pickedCompletion,
|
||||||
|
snippetCompletion,
|
||||||
|
} from '@codemirror/autocomplete'
|
||||||
|
import {
|
||||||
|
Facet,
|
||||||
|
StateEffect,
|
||||||
|
StateField,
|
||||||
|
Extension,
|
||||||
|
Annotation,
|
||||||
|
Transaction,
|
||||||
|
} from '@codemirror/state'
|
||||||
|
import {
|
||||||
|
EditorView,
|
||||||
|
Tooltip,
|
||||||
|
Decoration,
|
||||||
|
DecorationSet,
|
||||||
|
} from '@codemirror/view'
|
||||||
|
import { URI } from 'vscode-uri'
|
||||||
import {
|
import {
|
||||||
DiagnosticSeverity,
|
DiagnosticSeverity,
|
||||||
CompletionItemKind,
|
CompletionItemKind,
|
||||||
@ -21,49 +38,247 @@ import { LanguageServerClient } from 'editor/plugins/lsp'
|
|||||||
import { Marked } from '@ts-stack/markdown'
|
import { Marked } from '@ts-stack/markdown'
|
||||||
import { posToOffset } from 'editor/plugins/lsp/util'
|
import { posToOffset } from 'editor/plugins/lsp/util'
|
||||||
import { Program, ProgramMemory } from 'lang/wasm'
|
import { Program, ProgramMemory } from 'lang/wasm'
|
||||||
import { codeManager, editorManager, kclManager } from 'lib/singletons'
|
import { codeManager, editorManager } from 'lib/singletons'
|
||||||
import type { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
|
import type { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
|
||||||
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
|
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
|
||||||
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
|
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
|
||||||
|
import { copilotPluginEvent } from './copilot'
|
||||||
|
import { codeManagerUpdateEvent } from 'lang/codeManager'
|
||||||
|
import {
|
||||||
|
modelingMachineEvent,
|
||||||
|
updateOutsideEditorEvent,
|
||||||
|
setDiagnosticsEvent,
|
||||||
|
} from 'editor/manager'
|
||||||
|
import { SemanticToken, getTag } from 'editor/plugins/lsp/semantic_token'
|
||||||
|
import { highlightingFor } from '@codemirror/language'
|
||||||
|
|
||||||
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '')
|
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '')
|
||||||
export const documentUri = Facet.define<string, string>({ combine: useLast })
|
export const docPathFacet = Facet.define<string, string>({
|
||||||
|
combine: useLast,
|
||||||
|
})
|
||||||
export const languageId = Facet.define<string, string>({ combine: useLast })
|
export const languageId = Facet.define<string, string>({ combine: useLast })
|
||||||
export const workspaceFolders = Facet.define<
|
export const workspaceFolders = Facet.define<
|
||||||
LSP.WorkspaceFolder[],
|
LSP.WorkspaceFolder[],
|
||||||
LSP.WorkspaceFolder[]
|
LSP.WorkspaceFolder[]
|
||||||
>({ combine: useLast })
|
>({ combine: useLast })
|
||||||
|
|
||||||
|
enum LspAnnotation {
|
||||||
|
SemanticTokens = 'semantic-tokens',
|
||||||
|
}
|
||||||
|
|
||||||
|
const lspEvent = Annotation.define<LspAnnotation>()
|
||||||
|
export const lspSemanticTokensEvent = lspEvent.of(LspAnnotation.SemanticTokens)
|
||||||
|
|
||||||
const CompletionItemKindMap = Object.fromEntries(
|
const CompletionItemKindMap = Object.fromEntries(
|
||||||
Object.entries(CompletionItemKind).map(([key, value]) => [value, key])
|
Object.entries(CompletionItemKind).map(([key, value]) => [value, key])
|
||||||
) as Record<CompletionItemKind, string>
|
) as Record<CompletionItemKind, string>
|
||||||
|
|
||||||
const changesDelay = 600
|
const changesDelay = 600
|
||||||
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
||||||
const updateDelay = 100
|
const addToken = StateEffect.define<SemanticToken>({
|
||||||
|
map: (token: SemanticToken, change) => ({
|
||||||
|
...token,
|
||||||
|
from: change.mapPos(token.from),
|
||||||
|
to: change.mapPos(token.to),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const semanticTokenField = StateField.define<DecorationSet>({
|
||||||
|
create() {
|
||||||
|
return Decoration.none
|
||||||
|
},
|
||||||
|
update(highlights, tr) {
|
||||||
|
// Nothing can come before this line, this is very important!
|
||||||
|
// It makes sure the highlights are updated correctly for the changes.
|
||||||
|
highlights = highlights.map(tr.changes)
|
||||||
|
|
||||||
|
const isSemanticTokensEvent = tr.annotation(lspSemanticTokensEvent.type)
|
||||||
|
if (!isSemanticTokensEvent) {
|
||||||
|
return highlights
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any of the changes are addToken
|
||||||
|
const hasAddToken = tr.effects.some((e) => e.is(addToken))
|
||||||
|
if (hasAddToken) {
|
||||||
|
highlights = highlights.update({
|
||||||
|
filter: (from, to) => false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e of tr.effects)
|
||||||
|
if (e.is(addToken)) {
|
||||||
|
const tag = getTag(e.value)
|
||||||
|
const className = tag
|
||||||
|
? highlightingFor(tr.startState, [tag])
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (e.value.from < e.value.to && tag) {
|
||||||
|
if (className) {
|
||||||
|
highlights = highlights.update({
|
||||||
|
add: [
|
||||||
|
Decoration.mark({ class: className }).range(
|
||||||
|
e.value.from,
|
||||||
|
e.value.to
|
||||||
|
),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return highlights
|
||||||
|
},
|
||||||
|
provide: (f) => EditorView.decorations.from(f),
|
||||||
|
})
|
||||||
|
|
||||||
|
export enum TransactionAnnotation {
|
||||||
|
Diagnostics = 'diagnostics',
|
||||||
|
Remote = 'remote',
|
||||||
|
UserSelect = 'user.select',
|
||||||
|
UserInput = 'user.input',
|
||||||
|
UserMove = 'user.move',
|
||||||
|
UserDelete = 'user.delete',
|
||||||
|
UserUndo = 'user.undo',
|
||||||
|
UserRedo = 'user.redo',
|
||||||
|
|
||||||
|
Copoilot = 'copilot',
|
||||||
|
OutsideEditor = 'outsideEditor',
|
||||||
|
CodeManager = 'codeManager',
|
||||||
|
ModelingMachine = 'modelingMachineEvent',
|
||||||
|
LspSemanticTokens = 'lspSemanticTokensEvent',
|
||||||
|
|
||||||
|
PickedCompletion = 'pickedCompletion',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionInfo {
|
||||||
|
annotations: TransactionAnnotation[]
|
||||||
|
time: number | null
|
||||||
|
docChanged: boolean
|
||||||
|
addToHistory: boolean
|
||||||
|
inSnippet: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateInfo = (update: ViewUpdate): TransactionInfo[] => {
|
||||||
|
let transactionInfos: TransactionInfo[] = []
|
||||||
|
|
||||||
|
for (const tr of update.transactions) {
|
||||||
|
let annotations: TransactionAnnotation[] = []
|
||||||
|
|
||||||
|
if (tr.isUserEvent('select')) {
|
||||||
|
annotations.push(TransactionAnnotation.UserSelect)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.isUserEvent('input')) {
|
||||||
|
annotations.push(TransactionAnnotation.UserInput)
|
||||||
|
}
|
||||||
|
if (tr.isUserEvent('delete')) {
|
||||||
|
annotations.push(TransactionAnnotation.UserDelete)
|
||||||
|
}
|
||||||
|
if (tr.isUserEvent('undo')) {
|
||||||
|
annotations.push(TransactionAnnotation.UserUndo)
|
||||||
|
}
|
||||||
|
if (tr.isUserEvent('redo')) {
|
||||||
|
annotations.push(TransactionAnnotation.UserRedo)
|
||||||
|
}
|
||||||
|
if (tr.isUserEvent('move')) {
|
||||||
|
annotations.push(TransactionAnnotation.UserMove)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.annotation(pickedCompletion) !== undefined) {
|
||||||
|
annotations.push(TransactionAnnotation.PickedCompletion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.annotation(copilotPluginEvent.type) !== undefined) {
|
||||||
|
annotations.push(TransactionAnnotation.Copoilot)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.annotation(updateOutsideEditorEvent.type) !== undefined) {
|
||||||
|
annotations.push(TransactionAnnotation.OutsideEditor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.annotation(codeManagerUpdateEvent.type) !== undefined) {
|
||||||
|
annotations.push(TransactionAnnotation.CodeManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.annotation(modelingMachineEvent.type) !== undefined) {
|
||||||
|
annotations.push(TransactionAnnotation.ModelingMachine)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.annotation(lspSemanticTokensEvent.type) !== undefined) {
|
||||||
|
annotations.push(TransactionAnnotation.LspSemanticTokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.annotation(setDiagnosticsEvent.type) !== undefined) {
|
||||||
|
annotations.push(TransactionAnnotation.Diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.annotation(Transaction.remote) !== undefined) {
|
||||||
|
annotations.push(TransactionAnnotation.Remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionInfos.push({
|
||||||
|
annotations,
|
||||||
|
time: tr.annotation(Transaction.time) || null,
|
||||||
|
docChanged: tr.docChanged,
|
||||||
|
addToHistory: tr.annotation(Transaction.addToHistory) || false,
|
||||||
|
inSnippet: hasNextSnippetField(update.state),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactionInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RelevantUpdate {
|
||||||
|
overall: boolean
|
||||||
|
userSelect: boolean
|
||||||
|
time: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||||
|
const infos = updateInfo(update)
|
||||||
|
// Make sure we are not in a snippet
|
||||||
|
if (infos.some((info) => info.inSnippet)) {
|
||||||
|
return {
|
||||||
|
overall: false,
|
||||||
|
userSelect: false,
|
||||||
|
time: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
overall: infos.some(
|
||||||
|
(info) =>
|
||||||
|
info.docChanged ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserMove)
|
||||||
|
),
|
||||||
|
userSelect: infos.some((info) =>
|
||||||
|
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||||
|
),
|
||||||
|
time: infos.length ? infos[0].time : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class LanguageServerPlugin implements PluginValue {
|
export class LanguageServerPlugin implements PluginValue {
|
||||||
public client: LanguageServerClient
|
public client: LanguageServerClient
|
||||||
public documentUri: string
|
|
||||||
public languageId: string
|
|
||||||
public workspaceFolders: LSP.WorkspaceFolder[]
|
|
||||||
private documentVersion: number
|
private documentVersion: number
|
||||||
private foldingRanges: LSP.FoldingRange[] | null = null
|
private foldingRanges: LSP.FoldingRange[] | null = null
|
||||||
private viewUpdate: ViewUpdate | null = null
|
|
||||||
|
private previousSemanticTokens: SemanticToken[] = []
|
||||||
|
|
||||||
private _defferer = deferExecution((code: string) => {
|
private _defferer = deferExecution((code: string) => {
|
||||||
try {
|
try {
|
||||||
// Update the state (not the editor) with the new code.
|
// Update the state (not the editor) with the new code.
|
||||||
this.client.textDocumentDidChange({
|
this.client.textDocumentDidChange({
|
||||||
textDocument: {
|
textDocument: {
|
||||||
uri: this.documentUri,
|
uri: this.getDocUri(),
|
||||||
version: this.documentVersion++,
|
version: this.documentVersion++,
|
||||||
},
|
},
|
||||||
contentChanges: [{ text: code }],
|
contentChanges: [{ text: code }],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.viewUpdate) {
|
this.requestSemanticTokens(this.view)
|
||||||
editorManager.handleOnViewUpdate(this.viewUpdate)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
@ -75,41 +290,43 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
private allowHTMLContent: boolean
|
private allowHTMLContent: boolean
|
||||||
) {
|
) {
|
||||||
this.client = client
|
this.client = client
|
||||||
this.documentUri = this.view.state.facet(documentUri)
|
|
||||||
this.languageId = this.view.state.facet(languageId)
|
|
||||||
this.workspaceFolders = this.view.state.facet(workspaceFolders)
|
|
||||||
this.documentVersion = 0
|
this.documentVersion = 0
|
||||||
|
|
||||||
this.client.attachPlugin(this)
|
this.client.attachPlugin(this)
|
||||||
|
|
||||||
this.initialize({
|
this.initialize({
|
||||||
documentText: this.view.state.doc.toString(),
|
documentText: this.getDocText(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
update(viewUpdate: ViewUpdate) {
|
private getDocPath(view = this.view) {
|
||||||
this.viewUpdate = viewUpdate
|
return view.state.facet(docPathFacet)
|
||||||
if (!viewUpdate.docChanged) {
|
}
|
||||||
// debounce the view update.
|
private getDocText(view = this.view) {
|
||||||
// otherwise it is laggy for typing.
|
return view.state.doc.toString()
|
||||||
if (debounceTimer) {
|
|
||||||
clearTimeout(debounceTimer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debounceTimer = setTimeout(() => {
|
private getDocUri(view = this.view) {
|
||||||
editorManager.handleOnViewUpdate(viewUpdate)
|
return URI.file(this.getDocPath(view)).toString()
|
||||||
}, updateDelay)
|
}
|
||||||
|
|
||||||
|
private getLanguageId(view = this.view) {
|
||||||
|
return view.state.facet(languageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
update(viewUpdate: ViewUpdate) {
|
||||||
|
const isRelevant = relevantUpdate(viewUpdate)
|
||||||
|
if (!isRelevant.overall) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const newCode = this.view.state.doc.toString()
|
// If the doc didn't change we can return early.
|
||||||
|
if (!viewUpdate.docChanged) {
|
||||||
codeManager.code = newCode
|
return
|
||||||
codeManager.writeToFile()
|
}
|
||||||
kclManager.executeCode()
|
|
||||||
|
|
||||||
this.sendChange({
|
this.sendChange({
|
||||||
documentText: newCode,
|
documentText: viewUpdate.state.doc.toString(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,14 +338,17 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
if (this.client.initializePromise) {
|
if (this.client.initializePromise) {
|
||||||
await this.client.initializePromise
|
await this.client.initializePromise
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client.textDocumentDidOpen({
|
this.client.textDocumentDidOpen({
|
||||||
textDocument: {
|
textDocument: {
|
||||||
uri: this.documentUri,
|
uri: this.getDocUri(),
|
||||||
languageId: this.languageId,
|
languageId: this.getLanguageId(),
|
||||||
text: documentText,
|
text: documentText,
|
||||||
version: this.documentVersion,
|
version: this.documentVersion,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.requestSemanticTokens(this.view)
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendChange({ documentText }: { documentText: string }) {
|
async sendChange({ documentText }: { documentText: string }) {
|
||||||
@ -138,7 +358,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
requestDiagnostics(view: EditorView) {
|
requestDiagnostics(view: EditorView) {
|
||||||
this.sendChange({ documentText: view.state.doc.toString() })
|
this.sendChange({ documentText: this.getDocText() })
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestHoverTooltip(
|
async requestHoverTooltip(
|
||||||
@ -151,9 +371,9 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
this.sendChange({ documentText: view.state.doc.toString() })
|
this.sendChange({ documentText: this.getDocText() })
|
||||||
const result = await this.client.textDocumentHover({
|
const result = await this.client.textDocumentHover({
|
||||||
textDocument: { uri: this.documentUri },
|
textDocument: { uri: this.getDocUri() },
|
||||||
position: { line, character },
|
position: { line, character },
|
||||||
})
|
})
|
||||||
if (!result) return null
|
if (!result) return null
|
||||||
@ -181,7 +401,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
const result = await this.client.textDocumentFoldingRange({
|
const result = await this.client.textDocumentFoldingRange({
|
||||||
textDocument: { uri: this.documentUri },
|
textDocument: { uri: this.getDocUri() },
|
||||||
})
|
})
|
||||||
|
|
||||||
return result || null
|
return result || null
|
||||||
@ -228,9 +448,9 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
|
|
||||||
return await this.client.updateUnits({
|
return await this.client.updateUnits({
|
||||||
textDocument: {
|
textDocument: {
|
||||||
uri: this.documentUri,
|
uri: this.getDocUri(),
|
||||||
},
|
},
|
||||||
text: this.view.state.doc.toString(),
|
text: this.getDocText(),
|
||||||
units,
|
units,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -254,7 +474,6 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('[lsp] kcl: updated canExecute', canExecute, response)
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,14 +486,14 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
|
|
||||||
this.client.textDocumentDidChange({
|
this.client.textDocumentDidChange({
|
||||||
textDocument: {
|
textDocument: {
|
||||||
uri: this.documentUri,
|
uri: this.getDocUri(),
|
||||||
version: this.documentVersion++,
|
version: this.documentVersion++,
|
||||||
},
|
},
|
||||||
contentChanges: [{ text: this.view.state.doc.toString() }],
|
contentChanges: [{ text: this.getDocText() }],
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await this.client.textDocumentFormatting({
|
const result = await this.client.textDocumentFormatting({
|
||||||
textDocument: { uri: this.documentUri },
|
textDocument: { uri: this.getDocUri() },
|
||||||
options: {
|
options: {
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
insertSpaces: true,
|
insertSpaces: true,
|
||||||
@ -285,16 +504,8 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
if (!result) return null
|
if (!result) return null
|
||||||
|
|
||||||
for (let i = 0; i < result.length; i++) {
|
for (let i = 0; i < result.length; i++) {
|
||||||
const { range, newText } = result[i]
|
const { newText } = result[i]
|
||||||
this.view.dispatch({
|
codeManager.updateCodeStateEditor(newText)
|
||||||
changes: [
|
|
||||||
{
|
|
||||||
from: posToOffset(this.view.state.doc, range.start)!,
|
|
||||||
to: posToOffset(this.view.state.doc, range.end)!,
|
|
||||||
insert: newText,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,7 +531,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await this.client.textDocumentCompletion({
|
const result = await this.client.textDocumentCompletion({
|
||||||
textDocument: { uri: this.documentUri },
|
textDocument: { uri: this.getDocUri() },
|
||||||
position: { line, character },
|
position: { line, character },
|
||||||
context: {
|
context: {
|
||||||
triggerKind,
|
triggerKind,
|
||||||
@ -379,16 +590,107 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
return completeFromList(options)(context)
|
return completeFromList(options)(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parseSemanticTokens(view: EditorView, data: number[]) {
|
||||||
|
// decode the lsp semantic token types
|
||||||
|
const tokens = []
|
||||||
|
for (let i = 0; i < data.length; i += 5) {
|
||||||
|
tokens.push({
|
||||||
|
deltaLine: data[i],
|
||||||
|
startChar: data[i + 1],
|
||||||
|
length: data[i + 2],
|
||||||
|
tokenType: data[i + 3],
|
||||||
|
modifiers: data[i + 4],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the tokens into an array of {to, from, type} objects
|
||||||
|
const tokenTypes =
|
||||||
|
this.client.getServerCapabilities().semanticTokensProvider!.legend
|
||||||
|
.tokenTypes
|
||||||
|
const tokenModifiers =
|
||||||
|
this.client.getServerCapabilities().semanticTokensProvider!.legend
|
||||||
|
.tokenModifiers
|
||||||
|
const tokenRanges: any = []
|
||||||
|
let curLine = 0
|
||||||
|
let prevStart = 0
|
||||||
|
for (let i = 0; i < tokens.length; i++) {
|
||||||
|
const token = tokens[i]
|
||||||
|
const tokenType = tokenTypes[token.tokenType]
|
||||||
|
// get a list of modifiers
|
||||||
|
const tokenModifier = []
|
||||||
|
for (let j = 0; j < tokenModifiers.length; j++) {
|
||||||
|
if (token.modifiers & (1 << j)) {
|
||||||
|
tokenModifier.push(tokenModifiers[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.deltaLine !== 0) prevStart = 0
|
||||||
|
|
||||||
|
const tokenRange = {
|
||||||
|
from: posToOffset(view.state.doc, {
|
||||||
|
line: curLine + token.deltaLine,
|
||||||
|
character: prevStart + token.startChar,
|
||||||
|
})!,
|
||||||
|
to: posToOffset(view.state.doc, {
|
||||||
|
line: curLine + token.deltaLine,
|
||||||
|
character: prevStart + token.startChar + token.length,
|
||||||
|
})!,
|
||||||
|
type: tokenType,
|
||||||
|
modifiers: tokenModifier,
|
||||||
|
}
|
||||||
|
tokenRanges.push(tokenRange)
|
||||||
|
|
||||||
|
curLine += token.deltaLine
|
||||||
|
prevStart += token.startChar
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by from
|
||||||
|
tokenRanges.sort((a: any, b: any) => a.from - b.from)
|
||||||
|
return tokenRanges
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestSemanticTokens(view: EditorView) {
|
||||||
|
if (
|
||||||
|
!this.client.ready ||
|
||||||
|
!this.client.getServerCapabilities().semanticTokensProvider
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.client.textDocumentSemanticTokensFull({
|
||||||
|
textDocument: { uri: this.getDocUri() },
|
||||||
|
})
|
||||||
|
if (!result) return null
|
||||||
|
|
||||||
|
const { data } = result
|
||||||
|
this.previousSemanticTokens = this.parseSemanticTokens(view, data)
|
||||||
|
|
||||||
|
const effects: StateEffect<SemanticToken | Extension>[] =
|
||||||
|
this.previousSemanticTokens.map((tokenRange: any) =>
|
||||||
|
addToken.of(tokenRange)
|
||||||
|
)
|
||||||
|
|
||||||
|
view.dispatch({
|
||||||
|
effects,
|
||||||
|
|
||||||
|
annotations: [lspSemanticTokensEvent, Transaction.addToHistory.of(false)],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async processNotification(notification: LSP.NotificationMessage) {
|
async processNotification(notification: LSP.NotificationMessage) {
|
||||||
try {
|
try {
|
||||||
switch (notification.method) {
|
switch (notification.method) {
|
||||||
case 'textDocument/publishDiagnostics':
|
case 'textDocument/publishDiagnostics':
|
||||||
|
if (notification === undefined) break
|
||||||
|
if (notification.params === undefined) break
|
||||||
|
if (!notification.params) break
|
||||||
|
const params = notification.params as PublishDiagnosticsParams
|
||||||
|
if (!params) break
|
||||||
console.log(
|
console.log(
|
||||||
'[lsp] [window/publishDiagnostics]',
|
'[lsp] [window/publishDiagnostics]',
|
||||||
this.client.getName(),
|
this.client.getName(),
|
||||||
notification.params
|
params
|
||||||
)
|
)
|
||||||
const params = notification.params as PublishDiagnosticsParams
|
|
||||||
// this is sometimes slower than our actual typing.
|
// this is sometimes slower than our actual typing.
|
||||||
this.processDiagnostics(params)
|
this.processDiagnostics(params)
|
||||||
break
|
break
|
||||||
@ -420,7 +722,6 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
// The server has updated the memory, we should update elsewhere.
|
// The server has updated the memory, we should update elsewhere.
|
||||||
let updatedMemory = notification.params as ProgramMemory
|
let updatedMemory = notification.params as ProgramMemory
|
||||||
console.log('[lsp]: Updated Memory', updatedMemory)
|
console.log('[lsp]: Updated Memory', updatedMemory)
|
||||||
kclManager.programMemory = updatedMemory
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -429,7 +730,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processDiagnostics(params: PublishDiagnosticsParams) {
|
processDiagnostics(params: PublishDiagnosticsParams) {
|
||||||
if (params.uri !== this.documentUri) return
|
if (params.uri !== this.getDocUri()) return
|
||||||
|
|
||||||
const diagnostics = params.diagnostics
|
const diagnostics = params.diagnostics
|
||||||
.map(({ range, message, severity }) => ({
|
.map(({ range, message, severity }) => ({
|
||||||
@ -459,7 +760,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
|
|
||||||
this.view.dispatch(setDiagnostics(this.view.state, diagnostics))
|
editorManager.addDiagnostics(diagnostics)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
112
src/editor/plugins/lsp/semantic_token.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { Tag, tags } from '@lezer/highlight'
|
||||||
|
|
||||||
|
export interface SemanticToken {
|
||||||
|
from: number
|
||||||
|
to: number
|
||||||
|
type: string
|
||||||
|
modifiers: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTag(semanticToken: SemanticToken): Tag | null {
|
||||||
|
let tokenType = convertSemanticTokenTypeToCodeMirrorTag(semanticToken.type)
|
||||||
|
|
||||||
|
if (
|
||||||
|
semanticToken.modifiers === undefined ||
|
||||||
|
semanticToken.modifiers === null ||
|
||||||
|
semanticToken.modifiers.length === 0
|
||||||
|
) {
|
||||||
|
return tokenType
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let modifier of semanticToken.modifiers) {
|
||||||
|
tokenType = convertSemanticTokenToCodeMirrorTag(
|
||||||
|
'',
|
||||||
|
modifier,
|
||||||
|
tokenType || undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenType
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTagName(semanticToken: SemanticToken): string {
|
||||||
|
let tokenType = semanticToken.type
|
||||||
|
|
||||||
|
if (
|
||||||
|
semanticToken.modifiers === undefined ||
|
||||||
|
semanticToken.modifiers === null ||
|
||||||
|
semanticToken.modifiers.length === 0
|
||||||
|
) {
|
||||||
|
return tokenType
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let modifier of semanticToken.modifiers) {
|
||||||
|
tokenType = `${tokenType}.${modifier}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenType
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertSemanticTokenTypeToCodeMirrorTag(
|
||||||
|
tokenType: string
|
||||||
|
): Tag | null {
|
||||||
|
switch (tokenType) {
|
||||||
|
case 'keyword':
|
||||||
|
return tags.keyword
|
||||||
|
case 'variable':
|
||||||
|
return tags.variableName
|
||||||
|
case 'string':
|
||||||
|
return tags.string
|
||||||
|
case 'number':
|
||||||
|
return tags.number
|
||||||
|
case 'comment':
|
||||||
|
return tags.comment
|
||||||
|
case 'operator':
|
||||||
|
return tags.operator
|
||||||
|
case 'function':
|
||||||
|
return tags.function(tags.name)
|
||||||
|
case 'type':
|
||||||
|
return tags.typeName
|
||||||
|
case 'property':
|
||||||
|
return tags.propertyName
|
||||||
|
case 'parameter':
|
||||||
|
return tags.local(tags.name)
|
||||||
|
default:
|
||||||
|
console.error('Unknown token type:', tokenType)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertSemanticTokenToCodeMirrorTag(
|
||||||
|
tokenType: string,
|
||||||
|
tokenModifier: string,
|
||||||
|
givenTag?: Tag
|
||||||
|
): Tag | null {
|
||||||
|
let tag = givenTag
|
||||||
|
? givenTag
|
||||||
|
: convertSemanticTokenTypeToCodeMirrorTag(tokenType)
|
||||||
|
|
||||||
|
if (!tag) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenModifier) {
|
||||||
|
switch (tokenModifier) {
|
||||||
|
case 'definition':
|
||||||
|
return tags.definition(tag)
|
||||||
|
case 'declaration':
|
||||||
|
return tags.definition(tag)
|
||||||
|
case 'readonly':
|
||||||
|
return tags.constant(tag)
|
||||||
|
case 'static':
|
||||||
|
return tags.constant(tag)
|
||||||
|
case 'defaultLibrary':
|
||||||
|
return tags.standard(tag)
|
||||||
|
default:
|
||||||
|
console.error('Unknown token modifier:', tokenModifier)
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag
|
||||||
|
}
|
@ -6,10 +6,13 @@ import { isTauri } from 'lib/isTauri'
|
|||||||
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { editorManager } from 'lib/singletons'
|
import { editorManager } from 'lib/singletons'
|
||||||
import { KeyBinding } from '@uiw/react-codemirror'
|
import { Annotation, KeyBinding, Transaction } from '@uiw/react-codemirror'
|
||||||
|
|
||||||
const PERSIST_CODE_TOKEN = 'persistCode'
|
const PERSIST_CODE_TOKEN = 'persistCode'
|
||||||
|
|
||||||
|
const codeManagerUpdateAnnotation = Annotation.define<null>()
|
||||||
|
export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(null)
|
||||||
|
|
||||||
export default class CodeManager {
|
export default class CodeManager {
|
||||||
private _code: string = bracket
|
private _code: string = bracket
|
||||||
#updateState: (arg: string) => void = () => {}
|
#updateState: (arg: string) => void = () => {}
|
||||||
@ -90,6 +93,10 @@ export default class CodeManager {
|
|||||||
to: editorManager.editorView.state.doc.length,
|
to: editorManager.editorView.state.doc.length,
|
||||||
insert: code,
|
insert: code,
|
||||||
},
|
},
|
||||||
|
annotations: [
|
||||||
|
codeManagerUpdateEvent,
|
||||||
|
Transaction.addToHistory.of(true),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import {
|
|||||||
createPipeSubstitution,
|
createPipeSubstitution,
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { warn } from 'node:console'
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await initPromise
|
await initPromise
|
||||||
|
@ -282,8 +282,10 @@ function moreNodePathFromSourceRange(
|
|||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_node.type === 'PipeSubstitution' && isInRange) return path
|
if (_node.type === 'PipeSubstitution' && isInRange) return path
|
||||||
console.error('not implemented: ' + node.type)
|
console.error('not implemented: ' + node.type)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
src/wasm-lib/Cargo.lock
generated
@ -712,7 +712,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
version = "0.1.19"
|
version = "0.1.20"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -1385,7 +1385,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.68"
|
version = "0.1.69"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
@ -1453,7 +1453,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
description = "A tool for generating documentation from Rust derive macros"
|
description = "A tool for generating documentation from Rust derive macros"
|
||||||
version = "0.1.19"
|
version = "0.1.20"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
description = "A test server for KCL"
|
description = "A test server for KCL"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.1.68"
|
version = "0.1.69"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
@ -19,7 +19,7 @@ chrono = "0.4.38"
|
|||||||
clap = { version = "4.5.7", default-features = false, optional = true }
|
clap = { version = "4.5.7", default-features = false, optional = true }
|
||||||
dashmap = "6.0.1"
|
dashmap = "6.0.1"
|
||||||
databake = { version = "0.1.8", features = ["derive"] }
|
databake = { version = "0.1.8", features = ["derive"] }
|
||||||
derive-docs = { version = "0.1.19", path = "../derive-docs" }
|
derive-docs = { version = "0.1.20", path = "../derive-docs" }
|
||||||
form_urlencoded = "1.2.1"
|
form_urlencoded = "1.2.1"
|
||||||
futures = { version = "0.3.30" }
|
futures = { version = "0.3.30" }
|
||||||
git_rev = "0.1.0"
|
git_rev = "0.1.0"
|
||||||
|
@ -70,11 +70,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("on_change after check: {:?}", params);
|
|
||||||
|
|
||||||
self.insert_code_map(params.uri.to_string(), params.text.as_bytes().to_vec())
|
self.insert_code_map(params.uri.to_string(), params.text.as_bytes().to_vec())
|
||||||
.await;
|
.await;
|
||||||
println!("on_change after insert: {:?}", params);
|
|
||||||
self.inner_on_change(params, false).await;
|
self.inner_on_change(params, false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,11 +39,10 @@ use tower_lsp::{
|
|||||||
Client, LanguageServer,
|
Client, LanguageServer,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use crate::lint::checks;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::types::{Value, VariableKind},
|
ast::types::{Value, VariableKind},
|
||||||
executor::SourceRange,
|
executor::SourceRange,
|
||||||
|
lint::checks,
|
||||||
lsp::{backend::Backend as _, util::IntoDiagnostic},
|
lsp::{backend::Backend as _, util::IntoDiagnostic},
|
||||||
parser::PIPE_OPERATOR,
|
parser::PIPE_OPERATOR,
|
||||||
token::TokenType,
|
token::TokenType,
|
||||||
@ -269,8 +268,6 @@ impl crate::lsp::backend::Backend for Backend {
|
|||||||
// Update our semantic tokens.
|
// Update our semantic tokens.
|
||||||
self.update_semantic_tokens(&tokens, ¶ms).await;
|
self.update_semantic_tokens(&tokens, ¶ms).await;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
{
|
|
||||||
let discovered_findings = ast
|
let discovered_findings = ast
|
||||||
.lint(checks::lint_variables)
|
.lint(checks::lint_variables)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -278,7 +275,6 @@ impl crate::lsp::backend::Backend for Backend {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
self.add_to_diagnostics(¶ms, &discovered_findings, false).await;
|
self.add_to_diagnostics(¶ms, &discovered_findings, false).await;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Send the notification to the client that the ast was updated.
|
// Send the notification to the client that the ast was updated.
|
||||||
if self.can_execute().await || self.executor_ctx().await.is_none() {
|
if self.can_execute().await || self.executor_ctx().await.is_none() {
|
||||||
@ -533,9 +529,9 @@ impl Backend {
|
|||||||
diagnostics: &[DiagT],
|
diagnostics: &[DiagT],
|
||||||
clear_all_before_add: bool,
|
clear_all_before_add: bool,
|
||||||
) {
|
) {
|
||||||
self.client
|
if diagnostics.is_empty() {
|
||||||
.log_message(MessageType::INFO, format!("adding {:?} to diag", diagnostics))
|
return;
|
||||||
.await;
|
}
|
||||||
|
|
||||||
if clear_all_before_add {
|
if clear_all_before_add {
|
||||||
self.clear_diagnostics_map(¶ms.uri, None).await;
|
self.clear_diagnostics_map(¶ms.uri, None).await;
|
||||||
@ -645,20 +641,6 @@ impl Backend {
|
|||||||
modifier
|
modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn completions_get_variables_from_ast(&self, file_name: &str) -> Vec<CompletionItem> {
|
|
||||||
let ast = match self.ast_map.get(file_name) {
|
|
||||||
Some(ast) => ast,
|
|
||||||
None => return vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the completion items.
|
|
||||||
match ast.completion_items() {
|
|
||||||
Ok(items) => items,
|
|
||||||
// TODO: don't ignore an error here.
|
|
||||||
Err(_err) => vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_zip(&self) -> Result<Vec<u8>> {
|
pub async fn create_zip(&self) -> Result<Vec<u8>> {
|
||||||
// Collect all the file data we know.
|
// Collect all the file data we know.
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
@ -1055,9 +1037,34 @@ impl LanguageServer for Backend {
|
|||||||
|
|
||||||
completions.extend(self.stdlib_completions.values().cloned());
|
completions.extend(self.stdlib_completions.values().cloned());
|
||||||
|
|
||||||
let variables = self
|
// Add more to the completions if we have more.
|
||||||
.completions_get_variables_from_ast(params.text_document_position.text_document.uri.as_ref())
|
let Some(ast) = self
|
||||||
.await;
|
.ast_map
|
||||||
|
.get(params.text_document_position.text_document.uri.as_ref())
|
||||||
|
else {
|
||||||
|
return Ok(Some(CompletionResponse::Array(completions)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(current_code) = self
|
||||||
|
.code_map
|
||||||
|
.get(params.text_document_position.text_document.uri.as_ref())
|
||||||
|
else {
|
||||||
|
return Ok(Some(CompletionResponse::Array(completions)));
|
||||||
|
};
|
||||||
|
let Ok(current_code) = std::str::from_utf8(¤t_code) else {
|
||||||
|
return Ok(Some(CompletionResponse::Array(completions)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let position = position_to_char_index(params.text_document_position.position, current_code);
|
||||||
|
if ast.get_non_code_meta_for_position(position).is_some() {
|
||||||
|
// If we are in a code comment we don't want to show completions.
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the completion items forem the ast.
|
||||||
|
let Ok(variables) = ast.completion_items() else {
|
||||||
|
return Ok(Some(CompletionResponse::Array(completions)));
|
||||||
|
};
|
||||||
|
|
||||||
// Get our variables from our AST to include in our completions.
|
// Get our variables from our AST to include in our completions.
|
||||||
completions.extend(variables);
|
completions.extend(variables);
|
||||||
|
@ -660,6 +660,41 @@ st"#
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_kcl_lsp_completions_empty_in_comment() {
|
||||||
|
let server = kcl_lsp_server(false).await.unwrap();
|
||||||
|
|
||||||
|
// Send open file.
|
||||||
|
server
|
||||||
|
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
|
||||||
|
text_document: tower_lsp::lsp_types::TextDocumentItem {
|
||||||
|
uri: "file:///test.kcl".try_into().unwrap(),
|
||||||
|
language_id: "kcl".to_string(),
|
||||||
|
version: 1,
|
||||||
|
text: r#"const thing= 1 // st"#.to_string(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Send completion request.
|
||||||
|
let completions = server
|
||||||
|
.completion(tower_lsp::lsp_types::CompletionParams {
|
||||||
|
text_document_position: tower_lsp::lsp_types::TextDocumentPositionParams {
|
||||||
|
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||||
|
uri: "file:///test.kcl".try_into().unwrap(),
|
||||||
|
},
|
||||||
|
position: tower_lsp::lsp_types::Position { line: 0, character: 19 },
|
||||||
|
},
|
||||||
|
context: None,
|
||||||
|
partial_result_params: Default::default(),
|
||||||
|
work_done_progress_params: Default::default(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(completions.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_kcl_lsp_completions_tags() {
|
async fn test_kcl_lsp_completions_tags() {
|
||||||
let server = kcl_lsp_server(false).await.unwrap();
|
let server = kcl_lsp_server(false).await.unwrap();
|
||||||
|
@ -93,6 +93,8 @@ impl TryFrom<TokenType> for SemanticTokenType {
|
|||||||
|
|
||||||
impl TokenType {
|
impl TokenType {
|
||||||
// This is for the lsp server.
|
// This is for the lsp server.
|
||||||
|
// Don't call this function directly in the code use a lazy_static instead
|
||||||
|
// like we do in the lsp server.
|
||||||
pub fn all_semantic_token_types() -> Result<Vec<SemanticTokenType>> {
|
pub fn all_semantic_token_types() -> Result<Vec<SemanticTokenType>> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = true;
|
settings.inline_subschemas = true;
|
||||||
|
@ -9,7 +9,13 @@ const config = defineConfig({
|
|||||||
open: true,
|
open: true,
|
||||||
port: 3000,
|
port: 3000,
|
||||||
watch: {
|
watch: {
|
||||||
ignored: ['**/target/**'],
|
ignored: [
|
||||||
|
'**/target/**',
|
||||||
|
'**/dist/**',
|
||||||
|
'**/build/**',
|
||||||
|
'**/test-results/**',
|
||||||
|
'**/playwright-report/**',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
|
199
yarn.lock
@ -1165,7 +1165,7 @@
|
|||||||
"@babel/helper-validator-identifier" "^7.24.7"
|
"@babel/helper-validator-identifier" "^7.24.7"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.16.0":
|
"@codemirror/autocomplete@^6.0.0":
|
||||||
version "6.16.2"
|
version "6.16.2"
|
||||||
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.16.2.tgz#ac4e191cd599503e45f35e97366b432d30b8f37a"
|
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.16.2.tgz#ac4e191cd599503e45f35e97366b432d30b8f37a"
|
||||||
integrity sha512-MjfDrHy0gHKlPWsvSsikhO1+BOh+eBHNgfH1OXs1+DAf30IonQldgMM3kxLDTG9ktE7kDLaA1j/l7KMPA4KNfw==
|
integrity sha512-MjfDrHy0gHKlPWsvSsikhO1+BOh+eBHNgfH1OXs1+DAf30IonQldgMM3kxLDTG9ktE7kDLaA1j/l7KMPA4KNfw==
|
||||||
@ -1175,7 +1175,17 @@
|
|||||||
"@codemirror/view" "^6.17.0"
|
"@codemirror/view" "^6.17.0"
|
||||||
"@lezer/common" "^1.0.0"
|
"@lezer/common" "^1.0.0"
|
||||||
|
|
||||||
"@codemirror/commands@^6.0.0", "@codemirror/commands@^6.1.0":
|
"@codemirror/autocomplete@^6.16.3":
|
||||||
|
version "6.16.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.16.3.tgz#04d5a4e4e44ccae1ba525d47db53a5479bf46338"
|
||||||
|
integrity sha512-Vl/tIeRVVUCRDuOG48lttBasNQu8usGgXQawBXI7WJAiUDSFOfzflmEsZFZo48mAvAaa4FZ/4/yLLxFtdJaKYA==
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/language" "^6.0.0"
|
||||||
|
"@codemirror/state" "^6.0.0"
|
||||||
|
"@codemirror/view" "^6.17.0"
|
||||||
|
"@lezer/common" "^1.0.0"
|
||||||
|
|
||||||
|
"@codemirror/commands@^6.0.0", "@codemirror/commands@^6.1.0", "@codemirror/commands@^6.6.0":
|
||||||
version "6.6.0"
|
version "6.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.6.0.tgz#d308f143fe1b8896ca25fdb855f66acdaf019dd4"
|
resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.6.0.tgz#d308f143fe1b8896ca25fdb855f66acdaf019dd4"
|
||||||
integrity sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==
|
integrity sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==
|
||||||
@ -1185,7 +1195,7 @@
|
|||||||
"@codemirror/view" "^6.27.0"
|
"@codemirror/view" "^6.27.0"
|
||||||
"@lezer/common" "^1.1.0"
|
"@lezer/common" "^1.1.0"
|
||||||
|
|
||||||
"@codemirror/language@^6.0.0":
|
"@codemirror/language@^6.0.0", "@codemirror/language@^6.10.2":
|
||||||
version "6.10.2"
|
version "6.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.10.2.tgz#4056dc219619627ffe995832eeb09cea6060be61"
|
resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.10.2.tgz#4056dc219619627ffe995832eeb09cea6060be61"
|
||||||
integrity sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==
|
integrity sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==
|
||||||
@ -1206,7 +1216,16 @@
|
|||||||
"@codemirror/view" "^6.0.0"
|
"@codemirror/view" "^6.0.0"
|
||||||
crelt "^1.0.5"
|
crelt "^1.0.5"
|
||||||
|
|
||||||
"@codemirror/search@^6.0.0":
|
"@codemirror/lint@^6.8.1":
|
||||||
|
version "6.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.8.1.tgz#6427848815baaf68c08e98c7673b804d3d8c0e7f"
|
||||||
|
integrity sha512-IZ0Y7S4/bpaunwggW2jYqwLuHj0QtESf5xcROewY6+lDNwZ/NzvR4t+vpYgg9m7V8UXLPYqG+lu3DF470E5Oxg==
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/state" "^6.0.0"
|
||||||
|
"@codemirror/view" "^6.0.0"
|
||||||
|
crelt "^1.0.5"
|
||||||
|
|
||||||
|
"@codemirror/search@^6.0.0", "@codemirror/search@^6.5.6":
|
||||||
version "6.5.6"
|
version "6.5.6"
|
||||||
resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.5.6.tgz#8f858b9e678d675869112e475f082d1e8488db93"
|
resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.5.6.tgz#8f858b9e678d675869112e475f082d1e8488db93"
|
||||||
integrity sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==
|
integrity sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==
|
||||||
@ -1215,7 +1234,7 @@
|
|||||||
"@codemirror/view" "^6.0.0"
|
"@codemirror/view" "^6.0.0"
|
||||||
crelt "^1.0.5"
|
crelt "^1.0.5"
|
||||||
|
|
||||||
"@codemirror/state@^6.0.0", "@codemirror/state@^6.1.1", "@codemirror/state@^6.2.1", "@codemirror/state@^6.4.0":
|
"@codemirror/state@^6.0.0", "@codemirror/state@^6.1.1", "@codemirror/state@^6.2.1", "@codemirror/state@^6.4.0", "@codemirror/state@^6.4.1":
|
||||||
version "6.4.1"
|
version "6.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.4.1.tgz#da57143695c056d9a3c38705ed34136e2b68171b"
|
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.4.1.tgz#da57143695c056d9a3c38705ed34136e2b68171b"
|
||||||
integrity sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==
|
integrity sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==
|
||||||
@ -1568,28 +1587,19 @@
|
|||||||
ts-node "^10.9.1"
|
ts-node "^10.9.1"
|
||||||
tslib "~2.4"
|
tslib "~2.4"
|
||||||
|
|
||||||
"@lezer/common@^1.0.0", "@lezer/common@^1.1.0", "@lezer/common@^1.2.0":
|
"@lezer/common@^1.0.0", "@lezer/common@^1.1.0":
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.2.1.tgz#198b278b7869668e1bebbe687586e12a42731049"
|
resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.2.1.tgz#198b278b7869668e1bebbe687586e12a42731049"
|
||||||
integrity sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==
|
integrity sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==
|
||||||
|
|
||||||
"@lezer/highlight@^1.0.0", "@lezer/highlight@^1.1.3":
|
"@lezer/highlight@^1.0.0":
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.0.tgz#e5898c3644208b4b589084089dceeea2966f7780"
|
resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.0.tgz#e5898c3644208b4b589084089dceeea2966f7780"
|
||||||
integrity sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==
|
integrity sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@lezer/common" "^1.0.0"
|
"@lezer/common" "^1.0.0"
|
||||||
|
|
||||||
"@lezer/javascript@^1.4.9":
|
"@lezer/lr@^1.0.0":
|
||||||
version "1.4.17"
|
|
||||||
resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-1.4.17.tgz#8456e369f960c328b9e823342d0c72d704238c31"
|
|
||||||
integrity sha512-bYW4ctpyGK+JMumDApeUzuIezX01H76R1foD6LcRX224FWfyYit/HYxiPGDjXXe/wQWASjCvVGoukTH68+0HIA==
|
|
||||||
dependencies:
|
|
||||||
"@lezer/common" "^1.2.0"
|
|
||||||
"@lezer/highlight" "^1.1.3"
|
|
||||||
"@lezer/lr" "^1.3.0"
|
|
||||||
|
|
||||||
"@lezer/lr@^1.0.0", "@lezer/lr@^1.3.0":
|
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.1.tgz#fe25f051880a754e820b28148d90aa2a96b8bdd2"
|
resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.1.tgz#fe25f051880a754e820b28148d90aa2a96b8bdd2"
|
||||||
integrity sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==
|
integrity sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==
|
||||||
@ -1631,16 +1641,6 @@
|
|||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
"@open-rpc/client-js@^1.8.1":
|
|
||||||
version "1.8.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@open-rpc/client-js/-/client-js-1.8.1.tgz#73b5a5bf237f24b14c3c89205b1fca3aea213213"
|
|
||||||
integrity sha512-vV+Hetl688nY/oWI9IFY0iKDrWuLdYhf7OIKI6U1DcnJV7r4gAgwRJjEr1QVYszUc0gjkHoQJzqevmXMGLyA0g==
|
|
||||||
dependencies:
|
|
||||||
isomorphic-fetch "^3.0.0"
|
|
||||||
isomorphic-ws "^5.0.0"
|
|
||||||
strict-event-emitter-types "^2.0.0"
|
|
||||||
ws "^7.0.0"
|
|
||||||
|
|
||||||
"@pkgjs/parseargs@^0.11.0":
|
"@pkgjs/parseargs@^0.11.0":
|
||||||
version "0.11.0"
|
version "0.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||||
@ -2021,11 +2021,6 @@
|
|||||||
"@testing-library/dom" "^10.0.0"
|
"@testing-library/dom" "^10.0.0"
|
||||||
"@types/react-dom" "^18.0.0"
|
"@types/react-dom" "^18.0.0"
|
||||||
|
|
||||||
"@testing-library/user-event@^14.5.2":
|
|
||||||
version "14.5.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd"
|
|
||||||
integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==
|
|
||||||
|
|
||||||
"@tootallnate/quickjs-emscripten@^0.23.0":
|
"@tootallnate/quickjs-emscripten@^0.23.0":
|
||||||
version "0.23.0"
|
version "0.23.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c"
|
resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c"
|
||||||
@ -2101,16 +2096,6 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.20.7"
|
"@babel/types" "^7.20.7"
|
||||||
|
|
||||||
"@types/crypto-js@^4.2.2":
|
|
||||||
version "4.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.2.2.tgz#771c4a768d94eb5922cc202a3009558204df0cea"
|
|
||||||
integrity sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==
|
|
||||||
|
|
||||||
"@types/debounce-promise@^3.1.9":
|
|
||||||
version "3.1.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/debounce-promise/-/debounce-promise-3.1.9.tgz#b59346fe5c24636ebe0fb88f2f7e41b888b1cd7c"
|
|
||||||
integrity sha512-awNxydYSU+E2vL7EiOAMtiSOfL5gUM5X4YSE2A92qpxDJQ/rXz6oMPYBFDcDywlUmvIDI6zsqgq17cGm5CITQw==
|
|
||||||
|
|
||||||
"@types/eslint@^8.4.5":
|
"@types/eslint@^8.4.5":
|
||||||
version "8.56.10"
|
version "8.56.10"
|
||||||
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d"
|
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d"
|
||||||
@ -2124,14 +2109,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
||||||
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||||
|
|
||||||
"@types/hoist-non-react-statics@^3.3.1":
|
|
||||||
version "3.3.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494"
|
|
||||||
integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==
|
|
||||||
dependencies:
|
|
||||||
"@types/react" "*"
|
|
||||||
hoist-non-react-statics "^3.3.0"
|
|
||||||
|
|
||||||
"@types/http-cache-semantics@^4.0.2":
|
"@types/http-cache-semantics@^4.0.2":
|
||||||
version "4.0.4"
|
version "4.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4"
|
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4"
|
||||||
@ -3502,7 +3479,7 @@ clone@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||||
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
|
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
|
||||||
|
|
||||||
codemirror@^6.0.0:
|
codemirror@^6.0.0, codemirror@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29"
|
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29"
|
||||||
integrity sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==
|
integrity sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==
|
||||||
@ -3671,11 +3648,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
|||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
crypto-js@^4.2.0:
|
|
||||||
version "4.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
|
|
||||||
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
|
|
||||||
|
|
||||||
css-line-break@^2.1.0:
|
css-line-break@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
|
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
|
||||||
@ -3755,11 +3727,6 @@ data-view-byte-offset@^1.0.0:
|
|||||||
es-errors "^1.3.0"
|
es-errors "^1.3.0"
|
||||||
is-data-view "^1.0.1"
|
is-data-view "^1.0.1"
|
||||||
|
|
||||||
debounce-promise@^3.1.2:
|
|
||||||
version "3.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/debounce-promise/-/debounce-promise-3.1.2.tgz#320fb8c7d15a344455cd33cee5ab63530b6dc7c5"
|
|
||||||
integrity sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==
|
|
||||||
|
|
||||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
||||||
version "4.3.5"
|
version "4.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e"
|
||||||
@ -3815,11 +3782,6 @@ deepmerge-ts@^5.0.0, deepmerge-ts@^5.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz#c55206cc4c7be2ded89b9c816cf3608884525d7a"
|
resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz#c55206cc4c7be2ded89b9c816cf3608884525d7a"
|
||||||
integrity sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==
|
integrity sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==
|
||||||
|
|
||||||
deepmerge@^2.1.1:
|
|
||||||
version "2.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
|
|
||||||
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
|
|
||||||
|
|
||||||
defaults@^1.0.3:
|
defaults@^1.0.3:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a"
|
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a"
|
||||||
@ -4752,20 +4714,6 @@ formdata-polyfill@^4.0.10:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fetch-blob "^3.1.2"
|
fetch-blob "^3.1.2"
|
||||||
|
|
||||||
formik@^2.4.6:
|
|
||||||
version "2.4.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.6.tgz#4da75ca80f1a827ab35b08fd98d5a76e928c9686"
|
|
||||||
integrity sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==
|
|
||||||
dependencies:
|
|
||||||
"@types/hoist-non-react-statics" "^3.3.1"
|
|
||||||
deepmerge "^2.1.1"
|
|
||||||
hoist-non-react-statics "^3.3.0"
|
|
||||||
lodash "^4.17.21"
|
|
||||||
lodash-es "^4.17.21"
|
|
||||||
react-fast-compare "^2.0.1"
|
|
||||||
tiny-warning "^1.0.2"
|
|
||||||
tslib "^2.0.0"
|
|
||||||
|
|
||||||
fraction.js@^4.3.7:
|
fraction.js@^4.3.7:
|
||||||
version "4.3.7"
|
version "4.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
|
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
|
||||||
@ -5132,13 +5080,6 @@ he@1.2.0, he@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
hoist-non-react-statics@^3.3.0:
|
|
||||||
version "3.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
|
||||||
dependencies:
|
|
||||||
react-is "^16.7.0"
|
|
||||||
|
|
||||||
hosted-git-info@^7.0.0:
|
hosted-git-info@^7.0.0:
|
||||||
version "7.0.2"
|
version "7.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17"
|
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17"
|
||||||
@ -5153,7 +5094,7 @@ html-encoding-sniffer@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
whatwg-encoding "^2.0.0"
|
whatwg-encoding "^2.0.0"
|
||||||
|
|
||||||
html2canvas-pro@^1.4.3:
|
html2canvas-pro@^1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/html2canvas-pro/-/html2canvas-pro-1.5.0.tgz#18925def43505bad352a394b95fffb45d6d46a8f"
|
resolved "https://registry.yarnpkg.com/html2canvas-pro/-/html2canvas-pro-1.5.0.tgz#18925def43505bad352a394b95fffb45d6d46a8f"
|
||||||
integrity sha512-izxSphcINRwfEVV6eamsPVdhsxSYqX8n/hxzK+niVWdB+onM+aYRoVO+xgS9iMmZoUleZTWg1tJwryikib2hXg==
|
integrity sha512-izxSphcINRwfEVV6eamsPVdhsxSYqX8n/hxzK+niVWdB+onM+aYRoVO+xgS9iMmZoUleZTWg1tJwryikib2hXg==
|
||||||
@ -5581,11 +5522,6 @@ isomorphic-fetch@^3.0.0:
|
|||||||
node-fetch "^2.6.1"
|
node-fetch "^2.6.1"
|
||||||
whatwg-fetch "^3.4.1"
|
whatwg-fetch "^3.4.1"
|
||||||
|
|
||||||
isomorphic-ws@^5.0.0:
|
|
||||||
version "5.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf"
|
|
||||||
integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==
|
|
||||||
|
|
||||||
iterator.prototype@^1.1.2:
|
iterator.prototype@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0"
|
resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0"
|
||||||
@ -5884,11 +5820,6 @@ locate-path@^7.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate "^6.0.0"
|
p-locate "^6.0.0"
|
||||||
|
|
||||||
lodash-es@^4.17.21:
|
|
||||||
version "4.17.21"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
|
||||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
|
||||||
|
|
||||||
lodash.clonedeep@^4.5.0:
|
lodash.clonedeep@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||||
@ -6937,11 +6868,6 @@ react-dom@^18.2.0:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
scheduler "^0.23.2"
|
scheduler "^0.23.2"
|
||||||
|
|
||||||
react-fast-compare@^2.0.1:
|
|
||||||
version "2.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
|
||||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
|
||||||
|
|
||||||
react-hot-toast@^2.4.1:
|
react-hot-toast@^2.4.1:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.4.1.tgz#df04295eda8a7b12c4f968e54a61c8d36f4c0994"
|
resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.4.1.tgz#df04295eda8a7b12c4f968e54a61c8d36f4c0994"
|
||||||
@ -6954,7 +6880,7 @@ react-hotkeys-hook@^4.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz#807b389b15256daf6a813a1ec09e6698064fe97f"
|
resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz#807b389b15256daf6a813a1ec09e6698064fe97f"
|
||||||
integrity sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==
|
integrity sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.7.0:
|
react-is@^16.13.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
@ -7583,17 +7509,21 @@ streamx@^2.15.0, streamx@^2.18.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
bare-events "^2.2.0"
|
bare-events "^2.2.0"
|
||||||
|
|
||||||
strict-event-emitter-types@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz#05e15549cb4da1694478a53543e4e2f4abcf277f"
|
|
||||||
integrity sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==
|
|
||||||
|
|
||||||
string-natural-compare@^3.0.1:
|
string-natural-compare@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -7671,7 +7601,14 @@ string_decoder@~1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@ -7758,14 +7695,6 @@ supports-preserve-symlinks-flag@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||||
|
|
||||||
swr@^2.2.5:
|
|
||||||
version "2.2.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.5.tgz#063eea0e9939f947227d5ca760cc53696f46446b"
|
|
||||||
integrity sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==
|
|
||||||
dependencies:
|
|
||||||
client-only "^0.0.1"
|
|
||||||
use-sync-external-store "^1.2.0"
|
|
||||||
|
|
||||||
tailwindcss@^3.4.1:
|
tailwindcss@^3.4.1:
|
||||||
version "3.4.4"
|
version "3.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.4.tgz#351d932273e6abfa75ce7d226b5bf3a6cb257c05"
|
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.4.tgz#351d932273e6abfa75ce7d226b5bf3a6cb257c05"
|
||||||
@ -7883,11 +7812,6 @@ tiny-invariant@^1.3.3:
|
|||||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
|
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
|
||||||
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
|
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
|
||||||
|
|
||||||
tiny-warning@^1.0.2:
|
|
||||||
version "1.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
|
||||||
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
|
||||||
|
|
||||||
tinybench@^2.5.1:
|
tinybench@^2.5.1:
|
||||||
version "2.8.0"
|
version "2.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.8.0.tgz#30e19ae3a27508ee18273ffed9ac7018949acd7b"
|
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.8.0.tgz#30e19ae3a27508ee18273ffed9ac7018949acd7b"
|
||||||
@ -7932,7 +7856,7 @@ ts-interface-checker@^0.1.9:
|
|||||||
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
|
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
|
||||||
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
|
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
|
||||||
|
|
||||||
ts-node@^10.9.1, ts-node@^10.9.2:
|
ts-node@^10.9.1:
|
||||||
version "10.9.2"
|
version "10.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"
|
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"
|
||||||
integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==
|
integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==
|
||||||
@ -7971,7 +7895,7 @@ tslib@^1.8.1:
|
|||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0:
|
tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0:
|
||||||
version "2.6.3"
|
version "2.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
|
||||||
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
|
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
|
||||||
@ -8189,7 +8113,7 @@ use-sync-external-store@1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||||
|
|
||||||
use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0:
|
use-sync-external-store@^1.0.0:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9"
|
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9"
|
||||||
integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==
|
integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==
|
||||||
@ -8331,6 +8255,11 @@ vscode-languageserver-types@3.17.5:
|
|||||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a"
|
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a"
|
||||||
integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==
|
integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==
|
||||||
|
|
||||||
|
vscode-uri@^3.0.8:
|
||||||
|
version "3.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
|
||||||
|
integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
|
||||||
|
|
||||||
w3c-keyname@^2.2.4:
|
w3c-keyname@^2.2.4:
|
||||||
version "2.2.8"
|
version "2.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
|
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
|
||||||
@ -8552,7 +8481,7 @@ workerpool@6.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
|
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
|
||||||
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
|
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
@ -8570,6 +8499,15 @@ wrap-ansi@^6.2.0:
|
|||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
|
wrap-ansi@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.0.0"
|
||||||
|
string-width "^4.1.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^8.1.0:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
@ -8589,11 +8527,6 @@ ws@8.13.0:
|
|||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
|
||||||
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
|
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
|
||||||
|
|
||||||
ws@^7.0.0:
|
|
||||||
version "7.5.10"
|
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
|
|
||||||
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
|
|
||||||
|
|
||||||
ws@^8.17.0, ws@^8.8.0:
|
ws@^8.17.0, ws@^8.8.0:
|
||||||
version "8.17.1"
|
version "8.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
|
||||||
|