Fix LSP tooltip cutoff, style hover/autocomplete tooltips, add text wrapping setting (#404)
* Fix: allow tooltips to overflow code pane while keeping the same vertical and horizontal scroll behavior that we've had. Signed-off-by: Frank Noirot <frank@kittycad.io> * Style tooltips in light and dark mode * Fix: properly display autocomplete info as HTML We were parsing it from md to html, but displaying the parsed html as a string in the info box. Signed-off-by: Frank Noirot <frank@kittycad.io> * Fix z-index of command bar to show over code panel * Let user set text wrapping in editor * Style hover tooltips * Fix failing tests by not including line wrapping plugin in test mode --------- Signed-off-by: Frank Noirot <frank@kittycad.io>
This commit is contained in:
48
src/App.tsx
48
src/App.tsx
@ -10,7 +10,7 @@ import { DebugPanel } from './components/DebugPanel'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { asyncParser } from './lang/abstractSyntaxTree'
|
import { asyncParser } from './lang/abstractSyntaxTree'
|
||||||
import { _executor } from './lang/executor'
|
import { _executor } from './lang/executor'
|
||||||
import CodeMirror from '@uiw/react-codemirror'
|
import CodeMirror, { Extension } from '@uiw/react-codemirror'
|
||||||
import { linter, lintGutter } from '@codemirror/lint'
|
import { linter, lintGutter } from '@codemirror/lint'
|
||||||
import { ViewUpdate, EditorView } from '@codemirror/view'
|
import { ViewUpdate, EditorView } from '@codemirror/view'
|
||||||
import {
|
import {
|
||||||
@ -54,6 +54,7 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
|||||||
import { onboardingPaths } from 'routes/Onboarding'
|
import { onboardingPaths } from 'routes/Onboarding'
|
||||||
import { LanguageServerClient } from 'editor/lsp'
|
import { LanguageServerClient } from 'editor/lsp'
|
||||||
import kclLanguage from 'editor/lsp/language'
|
import kclLanguage from 'editor/lsp/language'
|
||||||
|
import { CSSRuleObject } from 'tailwindcss/types/config'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
||||||
@ -138,7 +139,7 @@ export function App() {
|
|||||||
context: { token },
|
context: { token },
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
context: { showDebugPanel, theme, onboardingStatus },
|
context: { showDebugPanel, theme, onboardingStatus, textWrapping },
|
||||||
},
|
},
|
||||||
} = useGlobalStateContext()
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
@ -422,17 +423,6 @@ export function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const extraExtensions = useMemo(() => {
|
|
||||||
if (TEST) return []
|
|
||||||
return [
|
|
||||||
lintGutter(),
|
|
||||||
linter((_view) => {
|
|
||||||
return kclErrToDiagnostic(useStore.getState().kclErrors)
|
|
||||||
}),
|
|
||||||
EditorView.lineWrapping,
|
|
||||||
]
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// So this is a bit weird, we need to initialize the lsp server and client.
|
// So this is a bit weird, we need to initialize the lsp server and client.
|
||||||
// But the server happens async so we break this into two parts.
|
// But the server happens async so we break this into two parts.
|
||||||
// Below is the client and server promise.
|
// Below is the client and server promise.
|
||||||
@ -472,6 +462,25 @@ export function App() {
|
|||||||
return plugin
|
return plugin
|
||||||
}, [lspClient, isLSPServerReady])
|
}, [lspClient, isLSPServerReady])
|
||||||
|
|
||||||
|
const editorExtensions = useMemo(() => {
|
||||||
|
const extensions = [lineHighlightField] as Extension[]
|
||||||
|
|
||||||
|
if (kclLSP) extensions.push(kclLSP)
|
||||||
|
|
||||||
|
// These extensions have proven to mess with vitest
|
||||||
|
if (!TEST) {
|
||||||
|
extensions.push(
|
||||||
|
lintGutter(),
|
||||||
|
linter((_view) => {
|
||||||
|
return kclErrToDiagnostic(useStore.getState().kclErrors)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
if (textWrapping === 'On') extensions.push(EditorView.lineWrapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensions
|
||||||
|
}, [kclLSP, textWrapping])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="h-screen overflow-hidden relative flex flex-col cursor-pointer select-none"
|
className="h-screen overflow-hidden relative flex flex-col cursor-pointer select-none"
|
||||||
@ -513,7 +522,7 @@ export function App() {
|
|||||||
<CollapsiblePanel
|
<CollapsiblePanel
|
||||||
title="Code"
|
title="Code"
|
||||||
icon={faCode}
|
icon={faCode}
|
||||||
className="open:!mb-2 overflow-x-hidden"
|
className="open:!mb-2"
|
||||||
open={openPanes.includes('code')}
|
open={openPanes.includes('code')}
|
||||||
>
|
>
|
||||||
<div className="px-2 py-1">
|
<div className="px-2 py-1">
|
||||||
@ -527,16 +536,13 @@ export function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="code-mirror-override"
|
id="code-mirror-override"
|
||||||
className="overflow-x-hidden h-full"
|
className="full-height-subtract"
|
||||||
|
style={{ '--height-subtract': '4.25rem' } as CSSRuleObject}
|
||||||
>
|
>
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
className="h-full overflow-hidden-x"
|
className="h-full"
|
||||||
value={code}
|
value={code}
|
||||||
extensions={
|
extensions={editorExtensions}
|
||||||
kclLSP
|
|
||||||
? [kclLSP, lineHighlightField, ...extraExtensions]
|
|
||||||
: [lineHighlightField, ...extraExtensions]
|
|
||||||
}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
theme={editorTheme}
|
theme={editorTheme}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.panel {
|
.panel {
|
||||||
@apply relative overflow-auto z-0;
|
@apply relative z-0;
|
||||||
@apply bg-chalkboard-10/70 backdrop-blur-sm;
|
@apply bg-chalkboard-10/70 backdrop-blur-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ const CommandBar = () => {
|
|||||||
setCommandBarOpen(false)
|
setCommandBarOpen(false)
|
||||||
clearState()
|
clearState()
|
||||||
}}
|
}}
|
||||||
className="fixed inset-0 overflow-y-auto p-4 pt-[25vh]"
|
className="fixed inset-0 z-40 overflow-y-auto p-4 pt-[25vh]"
|
||||||
>
|
>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
enter="duration-100 ease-out"
|
enter="duration-100 ease-out"
|
||||||
@ -207,7 +207,7 @@ const CommandBar = () => {
|
|||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
>
|
>
|
||||||
<Dialog.Overlay className="fixed z-40 inset-0 bg-chalkboard-10/70 dark:bg-chalkboard-110/50" />
|
<Dialog.Overlay className="fixed inset-0 bg-chalkboard-10/70 dark:bg-chalkboard-110/50" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
enter="duration-100 ease-out"
|
enter="duration-100 ease-out"
|
||||||
@ -221,7 +221,7 @@ const CommandBar = () => {
|
|||||||
<Combobox
|
<Combobox
|
||||||
value={selectedCommand}
|
value={selectedCommand}
|
||||||
onChange={handleCommandSelection}
|
onChange={handleCommandSelection}
|
||||||
className="rounded relative mx-auto z-40 p-2 bg-chalkboard-10 dark:bg-chalkboard-100 border dark:border-chalkboard-70 max-w-xl w-full shadow-lg"
|
className="rounded relative mx-auto p-2 bg-chalkboard-10 dark:bg-chalkboard-100 border dark:border-chalkboard-70 max-w-xl w-full shadow-lg"
|
||||||
as="div"
|
as="div"
|
||||||
>
|
>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
|
@ -208,7 +208,13 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
filterText: filterText ?? label,
|
filterText: filterText ?? label,
|
||||||
}
|
}
|
||||||
if (documentation) {
|
if (documentation) {
|
||||||
completion.info = formatContents(documentation)
|
completion.info = () => {
|
||||||
|
const htmlString = formatContents(documentation)
|
||||||
|
const htmlNode = document.createElement('div')
|
||||||
|
htmlNode.style.display = 'contents'
|
||||||
|
htmlNode.innerHTML = htmlString
|
||||||
|
return { dom: htmlNode }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return completion
|
return completion
|
||||||
|
@ -82,11 +82,22 @@ code {
|
|||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.full-height-subtract {
|
||||||
|
--height-subtract: 2.25rem;
|
||||||
|
height: 100%;
|
||||||
|
max-height: calc(100% - var(--height-subtract));
|
||||||
|
}
|
||||||
|
|
||||||
#code-mirror-override .cm-editor {
|
#code-mirror-override .cm-editor {
|
||||||
@apply bg-transparent;
|
@apply h-full bg-transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
#code-mirror-override .cm-scroller {
|
#code-mirror-override .cm-scroller {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
#code-mirror-override .cm-scroller::-webkit-scrollbar {
|
||||||
|
@apply h-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#code-mirror-override .cm-activeLine,
|
#code-mirror-override .cm-activeLine,
|
||||||
@ -137,14 +148,39 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#code-mirror-override .cm-tooltip {
|
#code-mirror-override .cm-tooltip {
|
||||||
font-size: 80%;
|
@apply text-xs shadow-md;
|
||||||
|
@apply bg-chalkboard-10 text-chalkboard-80;
|
||||||
|
@apply rounded-sm border-solid border border-chalkboard-40/30 border-l-liquid-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #code-mirror-override .cm-tooltip {
|
||||||
|
@apply bg-chalkboard-110 text-chalkboard-40;
|
||||||
|
@apply border-chalkboard-70/20 border-l-liquid-70;
|
||||||
}
|
}
|
||||||
|
|
||||||
#code-mirror-override .cm-tooltip-hover {
|
#code-mirror-override .cm-tooltip-hover {
|
||||||
|
@apply py-1 px-2 w-max max-w-md;
|
||||||
}
|
}
|
||||||
|
|
||||||
#code-mirror-override .cm-tooltip-hover .documentation {
|
#code-mirror-override .cm-completionInfo {
|
||||||
padding: 5;
|
@apply px-4 rounded-l-none;
|
||||||
|
@apply bg-chalkboard-10 text-liquid-90;
|
||||||
|
@apply border-liquid-40/30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #code-mirror-override .cm-completionInfo {
|
||||||
|
@apply bg-liquid-120 text-liquid-50;
|
||||||
|
@apply border-liquid-90/60;
|
||||||
|
}
|
||||||
|
|
||||||
|
#code-mirror-override .cm-tooltip-autocomplete li {
|
||||||
|
@apply px-2 py-1;
|
||||||
|
}
|
||||||
|
#code-mirror-override .cm-tooltip-autocomplete li[aria-selected='true'] {
|
||||||
|
@apply bg-liquid-10 text-liquid-110;
|
||||||
|
}
|
||||||
|
.dark #code-mirror-override .cm-tooltip-autocomplete li[aria-selected='true'] {
|
||||||
|
@apply bg-liquid-100 text-liquid-20;
|
||||||
}
|
}
|
||||||
|
|
||||||
#code-mirror-override .cm-content {
|
#code-mirror-override .cm-content {
|
||||||
|
@ -62,6 +62,17 @@ export const settingsCommandBarMeta: CommandBarMeta = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
'Set Text Wrapping': {
|
||||||
|
displayValue: (args: string[]) => 'Set whether text in the editor wraps',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: 'textWrapping',
|
||||||
|
type: 'select',
|
||||||
|
defaultValue: 'textWrapping',
|
||||||
|
options: [{ name: 'On' }, { name: 'Off' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
'Set Onboarding Status': {
|
'Set Onboarding Status': {
|
||||||
hide: 'both',
|
hide: 'both',
|
||||||
},
|
},
|
||||||
@ -78,6 +89,7 @@ export const settingsMachine = createMachine(
|
|||||||
unitSystem: UnitSystem.Imperial,
|
unitSystem: UnitSystem.Imperial,
|
||||||
baseUnit: 'in' as BaseUnit,
|
baseUnit: 'in' as BaseUnit,
|
||||||
defaultDirectory: '',
|
defaultDirectory: '',
|
||||||
|
textWrapping: 'On' as 'On' | 'Off',
|
||||||
showDebugPanel: false,
|
showDebugPanel: false,
|
||||||
onboardingStatus: '',
|
onboardingStatus: '',
|
||||||
},
|
},
|
||||||
@ -142,6 +154,17 @@ export const settingsMachine = createMachine(
|
|||||||
target: 'idle',
|
target: 'idle',
|
||||||
internal: true,
|
internal: true,
|
||||||
},
|
},
|
||||||
|
'Set Text Wrapping': {
|
||||||
|
actions: [
|
||||||
|
assign({
|
||||||
|
textWrapping: (_, event) => event.data.textWrapping,
|
||||||
|
}),
|
||||||
|
'persistSettings',
|
||||||
|
'toastSuccess',
|
||||||
|
],
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
},
|
||||||
'Toggle Debug Panel': {
|
'Toggle Debug Panel': {
|
||||||
actions: [
|
actions: [
|
||||||
assign({
|
assign({
|
||||||
@ -182,6 +205,7 @@ export const settingsMachine = createMachine(
|
|||||||
data: { unitSystem: UnitSystem }
|
data: { unitSystem: UnitSystem }
|
||||||
}
|
}
|
||||||
| { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } }
|
| { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } }
|
||||||
|
| { type: 'Set Text Wrapping'; data: { textWrapping: 'On' | 'Off' } }
|
||||||
| { type: 'Set Onboarding Status'; data: { onboardingStatus: string } }
|
| { type: 'Set Onboarding Status'; data: { onboardingStatus: string } }
|
||||||
| { type: 'Toggle Debug Panel' },
|
| { type: 'Toggle Debug Panel' },
|
||||||
},
|
},
|
||||||
|
@ -18,6 +18,7 @@ export interface Typegen0 {
|
|||||||
| 'Set Default Directory'
|
| 'Set Default Directory'
|
||||||
| 'Set Default Project Name'
|
| 'Set Default Project Name'
|
||||||
| 'Set Onboarding Status'
|
| 'Set Onboarding Status'
|
||||||
|
| 'Set Text Wrapping'
|
||||||
| 'Set Theme'
|
| 'Set Theme'
|
||||||
| 'Set Unit System'
|
| 'Set Unit System'
|
||||||
| 'Toggle Debug Panel'
|
| 'Toggle Debug Panel'
|
||||||
@ -26,6 +27,7 @@ export interface Typegen0 {
|
|||||||
| 'Set Default Directory'
|
| 'Set Default Directory'
|
||||||
| 'Set Default Project Name'
|
| 'Set Default Project Name'
|
||||||
| 'Set Onboarding Status'
|
| 'Set Onboarding Status'
|
||||||
|
| 'Set Text Wrapping'
|
||||||
| 'Set Theme'
|
| 'Set Theme'
|
||||||
| 'Set Unit System'
|
| 'Set Unit System'
|
||||||
| 'Toggle Debug Panel'
|
| 'Toggle Debug Panel'
|
||||||
@ -34,6 +36,7 @@ export interface Typegen0 {
|
|||||||
| 'Set Base Unit'
|
| 'Set Base Unit'
|
||||||
| 'Set Default Directory'
|
| 'Set Default Directory'
|
||||||
| 'Set Default Project Name'
|
| 'Set Default Project Name'
|
||||||
|
| 'Set Text Wrapping'
|
||||||
| 'Set Theme'
|
| 'Set Theme'
|
||||||
| 'Set Unit System'
|
| 'Set Unit System'
|
||||||
| 'Toggle Debug Panel'
|
| 'Toggle Debug Panel'
|
||||||
|
Reference in New Issue
Block a user