Use a CodeMirror tooltip for the signature help in lsp-client
This commit is contained in:
		@ -47,7 +47,7 @@ import {
 | 
			
		||||
import { isArray } from '../lib/utils'
 | 
			
		||||
import lspGoToDefinitionExt from './go-to-definition'
 | 
			
		||||
import lspRenameExt from './rename'
 | 
			
		||||
import lspSignatureHelpExt from './signature-help'
 | 
			
		||||
import { lspSignatureHelpExt, setSignatureTooltip } from './signature-help'
 | 
			
		||||
 | 
			
		||||
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '')
 | 
			
		||||
export const docPathFacet = Facet.define<string, string>({
 | 
			
		||||
@ -695,6 +695,8 @@ export class LanguageServerPlugin implements PluginValue {
 | 
			
		||||
 | 
			
		||||
      // Create the tooltip container
 | 
			
		||||
      const dom = this.createTooltipContainer()
 | 
			
		||||
      dom.className =
 | 
			
		||||
        'documentation hover-tooltip cm-tooltip cm-signature-tooltip'
 | 
			
		||||
 | 
			
		||||
      // Get active signature
 | 
			
		||||
      const activeSignatureIndex = result.activeSignature ?? 0
 | 
			
		||||
@ -769,42 +771,7 @@ export class LanguageServerPlugin implements PluginValue {
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if (tooltip) {
 | 
			
		||||
      // Create and show the tooltip manually
 | 
			
		||||
      const { pos: tooltipPos, create } = tooltip
 | 
			
		||||
      const tooltipView = create(view)
 | 
			
		||||
 | 
			
		||||
      const tooltipElement = document.createElement('div')
 | 
			
		||||
      tooltipElement.className =
 | 
			
		||||
        'documentation hover-tooltip cm-tooltip cm-signature-tooltip'
 | 
			
		||||
      tooltipElement.style.position = 'absolute'
 | 
			
		||||
      tooltipElement.style.zIndex = '99999999'
 | 
			
		||||
 | 
			
		||||
      tooltipElement.appendChild(tooltipView.dom)
 | 
			
		||||
 | 
			
		||||
      // Position the tooltip
 | 
			
		||||
      const coords = view.coordsAtPos(tooltipPos)
 | 
			
		||||
      if (coords) {
 | 
			
		||||
        tooltipElement.style.left = `${coords.left}px`
 | 
			
		||||
        tooltipElement.style.top = `${coords.bottom + 5}px`
 | 
			
		||||
 | 
			
		||||
        // Add to DOM
 | 
			
		||||
        document.body.appendChild(tooltipElement)
 | 
			
		||||
 | 
			
		||||
        // Remove after a delay or on editor changes
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          removeTooltip() // Use the function that also cleans up event listeners
 | 
			
		||||
        }, 10000) // Show for 10 seconds
 | 
			
		||||
 | 
			
		||||
        // Also remove on any user input
 | 
			
		||||
        const removeTooltip = () => {
 | 
			
		||||
          tooltipElement.remove()
 | 
			
		||||
          view.dom.removeEventListener('keydown', removeTooltip)
 | 
			
		||||
          view.dom.removeEventListener('mousedown', removeTooltip)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        view.dom.addEventListener('keydown', removeTooltip)
 | 
			
		||||
        view.dom.addEventListener('mousedown', removeTooltip)
 | 
			
		||||
      }
 | 
			
		||||
      view.dispatch({ effects: setSignatureTooltip.of(tooltip) })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,68 @@
 | 
			
		||||
import type { Extension } from '@codemirror/state'
 | 
			
		||||
import { Prec } from '@codemirror/state'
 | 
			
		||||
import type { ViewPlugin } from '@codemirror/view'
 | 
			
		||||
import { EditorView } from '@codemirror/view'
 | 
			
		||||
import { keymap } from '@codemirror/view'
 | 
			
		||||
import {
 | 
			
		||||
  type EditorState,
 | 
			
		||||
  type Extension,
 | 
			
		||||
  Prec,
 | 
			
		||||
  StateEffect,
 | 
			
		||||
  StateField,
 | 
			
		||||
} from '@codemirror/state'
 | 
			
		||||
import type { Tooltip } from '@codemirror/view'
 | 
			
		||||
import { type ViewPlugin, showTooltip } from '@codemirror/view'
 | 
			
		||||
import { EditorView, keymap } from '@codemirror/view'
 | 
			
		||||
import { syntaxTree } from '@codemirror/language'
 | 
			
		||||
import { type SyntaxNode } from '@lezer/common'
 | 
			
		||||
 | 
			
		||||
import type { LanguageServerPlugin } from './lsp'
 | 
			
		||||
 | 
			
		||||
export default function lspSignatureHelpExt(
 | 
			
		||||
export const setSignatureTooltip = StateEffect.define<Tooltip | null>()
 | 
			
		||||
 | 
			
		||||
function findParenthesized(
 | 
			
		||||
  state: EditorState,
 | 
			
		||||
  pos: number,
 | 
			
		||||
  side: 1 | 0 | -1 = 0
 | 
			
		||||
) {
 | 
			
		||||
  let context: SyntaxNode | null = syntaxTree(state).resolveInner(pos, side)
 | 
			
		||||
  while (context) {
 | 
			
		||||
    const open = context.firstChild
 | 
			
		||||
    if (
 | 
			
		||||
      open &&
 | 
			
		||||
      open.from == context.from &&
 | 
			
		||||
      open.to == context.from + 1 &&
 | 
			
		||||
      state.doc.sliceString(open.from, open.to) == '('
 | 
			
		||||
    )
 | 
			
		||||
      break
 | 
			
		||||
    context = context.parent
 | 
			
		||||
  }
 | 
			
		||||
  return context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const signatureTooltip = StateField.define<Tooltip | null>({
 | 
			
		||||
  create: () => null,
 | 
			
		||||
  update(value, tr) {
 | 
			
		||||
    for (let effect of tr.effects) {
 | 
			
		||||
      if (effect.is(setSignatureTooltip)) return effect.value
 | 
			
		||||
    }
 | 
			
		||||
    if (!value) return null
 | 
			
		||||
    if (tr.selection) {
 | 
			
		||||
      let parens = findParenthesized(tr.state, tr.selection.main.head)
 | 
			
		||||
      if (!parens || parens.from != value.pos) return null
 | 
			
		||||
    }
 | 
			
		||||
    return tr.docChanged
 | 
			
		||||
      ? { ...value, pos: tr.changes.mapPos(value.pos) }
 | 
			
		||||
      : value
 | 
			
		||||
  },
 | 
			
		||||
  provide: (f) => [
 | 
			
		||||
    showTooltip.from(f),
 | 
			
		||||
    EditorView.domEventHandlers({
 | 
			
		||||
      blur: (_, view) => {
 | 
			
		||||
        if (view.state.field(f)) {
 | 
			
		||||
          view.dispatch({ effects: setSignatureTooltip.of(null) })
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    }),
 | 
			
		||||
  ],
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export function lspSignatureHelpExt(
 | 
			
		||||
  plugin: ViewPlugin<LanguageServerPlugin>
 | 
			
		||||
): Extension {
 | 
			
		||||
  return [
 | 
			
		||||
@ -18,9 +74,13 @@ export default function lspSignatureHelpExt(
 | 
			
		||||
            const value = view.plugin(plugin)
 | 
			
		||||
            if (!value) return false
 | 
			
		||||
 | 
			
		||||
            const pos = view.state.selection.main.head
 | 
			
		||||
            const parens = findParenthesized(
 | 
			
		||||
              view.state,
 | 
			
		||||
              view.state.selection.main.head
 | 
			
		||||
            )
 | 
			
		||||
            if (!parens) return false
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | 
			
		||||
            value.showSignatureHelpTooltip(view, pos)
 | 
			
		||||
            value.showSignatureHelpTooltip(view, parens.from)
 | 
			
		||||
            return true
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
@ -66,12 +126,16 @@ export default function lspSignatureHelpExt(
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      if (triggerPos >= 0) {
 | 
			
		||||
        await value.showSignatureHelpTooltip(
 | 
			
		||||
          update.view,
 | 
			
		||||
          triggerPos,
 | 
			
		||||
          triggerCharacter
 | 
			
		||||
        )
 | 
			
		||||
        const parens = findParenthesized(update.view.state, triggerPos, -1)
 | 
			
		||||
        if (parens) {
 | 
			
		||||
          await value.showSignatureHelpTooltip(
 | 
			
		||||
            update.view,
 | 
			
		||||
            parens.from,
 | 
			
		||||
            triggerCharacter
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }),
 | 
			
		||||
    signatureTooltip,
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user