HoppVariable.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import { watch, Ref } from "@nuxtjs/composition-api"
  2. import { Compartment } from "@codemirror/state"
  3. import { hoverTooltip } from "@codemirror/tooltip"
  4. import {
  5. Decoration,
  6. EditorView,
  7. MatchDecorator,
  8. ViewPlugin,
  9. } from "@codemirror/view"
  10. import * as E from "fp-ts/Either"
  11. import { HoppRESTVar, parseTemplateStringE } from "@hoppscotch/data"
  12. const HOPP_ENVIRONMENT_REGEX = /({{\w+}})/g
  13. const HOPP_ENV_HIGHLIGHT =
  14. "cursor-help transition rounded px-1 focus:outline-none mx-0.5 env-highlight"
  15. const HOPP_ENV_HIGHLIGHT_FOUND =
  16. "bg-accentDark text-accentContrast hover:bg-accent"
  17. const HOPP_ENV_HIGHLIGHT_NOT_FOUND =
  18. "bg-red-500 text-accentContrast hover:bg-red-600"
  19. const cursorTooltipField = (aggregateEnvs: HoppRESTVar[]) =>
  20. hoverTooltip(
  21. (view, pos, side) => {
  22. const { from, to, text } = view.state.doc.lineAt(pos)
  23. // TODO: When Codemirror 6 allows this to work (not make the
  24. // popups appear half of the time) use this implementation
  25. // const wordSelection = view.state.wordAt(pos)
  26. // if (!wordSelection) return null
  27. // const word = view.state.doc.sliceString(
  28. // wordSelection.from - 2,
  29. // wordSelection.to + 2
  30. // )
  31. // if (!HOPP_ENVIRONMENT_REGEX.test(word)) return null
  32. // Tracking the start and the end of the words
  33. let start = pos
  34. let end = pos
  35. while (start > from && /\w/.test(text[start - from - 1])) start--
  36. while (end < to && /\w/.test(text[end - from])) end++
  37. if (
  38. (start === pos && side < 0) ||
  39. (end === pos && side > 0) ||
  40. !HOPP_ENVIRONMENT_REGEX.test(
  41. text.slice(start - from - 2, end - from + 2)
  42. )
  43. )
  44. return null
  45. const envValue =
  46. aggregateEnvs.find(
  47. (env) => env.key === text.slice(start - from, end - from)
  48. // env.key === word.slice(wordSelection.from + 2, wordSelection.to - 2)
  49. )?.value ?? "not found"
  50. const result = parseTemplateStringE(envValue, aggregateEnvs)
  51. const finalEnv = E.isLeft(result) ? "error" : result.right
  52. return {
  53. pos: start,
  54. end: to,
  55. above: true,
  56. arrow: true,
  57. create() {
  58. const dom = document.createElement("span")
  59. const xmp = document.createElement("xmp")
  60. xmp.textContent = finalEnv
  61. dom.appendChild(xmp)
  62. dom.className = "tooltip-theme"
  63. return { dom }
  64. },
  65. }
  66. },
  67. // HACK: This is a hack to fix hover tooltip not coming half of the time
  68. // https://github.com/codemirror/tooltip/blob/765c463fc1d5afcc3ec93cee47d72606bed27e1d/src/tooltip.ts#L622
  69. // Still doesn't fix the not showing up some of the time issue, but this is atleast more consistent
  70. { hoverTime: 1 } as any
  71. )
  72. function checkEnv(env: string, aggregateEnvs: HoppRESTVar[]) {
  73. const className = aggregateEnvs.find(
  74. (k: { key: string }) => k.key === env.slice(2, -2)
  75. )
  76. ? HOPP_ENV_HIGHLIGHT_FOUND
  77. : HOPP_ENV_HIGHLIGHT_NOT_FOUND
  78. return Decoration.mark({
  79. class: `${HOPP_ENV_HIGHLIGHT} ${className}`,
  80. })
  81. }
  82. const getMatchDecorator = (aggregateEnvs: HoppRESTVar[]) =>
  83. new MatchDecorator({
  84. regexp: HOPP_ENVIRONMENT_REGEX,
  85. decoration: (m) => checkEnv(m[0], aggregateEnvs),
  86. })
  87. export const environmentHighlightStyle = (aggregateEnvs: HoppRESTVar[]) => {
  88. const decorator = getMatchDecorator(aggregateEnvs)
  89. return ViewPlugin.define(
  90. (view) => ({
  91. decorations: decorator.createDeco(view),
  92. update(u) {
  93. this.decorations = decorator.updateDeco(u, this.decorations)
  94. },
  95. }),
  96. {
  97. decorations: (v) => v.decorations,
  98. }
  99. )
  100. }
  101. export class HoppReactiveVarPlugin {
  102. private compartment = new Compartment()
  103. private envs: HoppRESTVar[] = []
  104. constructor(
  105. envsRef: Ref<HoppRESTVar[]>,
  106. private editorView: Ref<EditorView | undefined>
  107. ) {
  108. watch(
  109. envsRef,
  110. (envs) => {
  111. this.envs = envs
  112. this.editorView.value?.dispatch({
  113. effects: this.compartment.reconfigure([
  114. cursorTooltipField(this.envs),
  115. environmentHighlightStyle(this.envs),
  116. ]),
  117. })
  118. },
  119. { immediate: true }
  120. )
  121. }
  122. get extension() {
  123. return this.compartment.of([
  124. cursorTooltipField(this.envs),
  125. environmentHighlightStyle(this.envs),
  126. ])
  127. }
  128. }