HoppEnvironment.ts (hoppscotch-2.2.1) | : | HoppEnvironment.ts (hoppscotch-3.0.0) | ||
---|---|---|---|---|
import { watch, Ref } from "vue" | ||||
import { Compartment } from "@codemirror/state" | import { Compartment } from "@codemirror/state" | |||
import { hoverTooltip } from "@codemirror/tooltip" | ||||
import { | import { | |||
Decoration, | Decoration, | |||
EditorView, | EditorView, | |||
MatchDecorator, | MatchDecorator, | |||
ViewPlugin, | ViewPlugin, | |||
hoverTooltip, | ||||
} from "@codemirror/view" | } from "@codemirror/view" | |||
import { Ref } from "@nuxtjs/composition-api" | import * as E from "fp-ts/Either" | |||
import { StreamSubscriberFunc } from "~/helpers/utils/composables" | import { parseTemplateStringE } from "@hoppscotch/data" | |||
import { StreamSubscriberFunc } from "@composables/stream" | ||||
import { | import { | |||
AggregateEnvironment, | AggregateEnvironment, | |||
aggregateEnvs$, | aggregateEnvs$, | |||
getAggregateEnvs, | getAggregateEnvs, | |||
} from "~/newstore/environments" | } from "~/newstore/environments" | |||
const HOPP_ENVIRONMENT_REGEX = /(<<\w+>>)/g | const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g | |||
const HOPP_ENV_HIGHLIGHT = | const HOPP_ENV_HIGHLIGHT = | |||
"cursor-help transition rounded px-1 focus:outline-none mx-0.5" | "cursor-help transition rounded px-1 focus:outline-none mx-0.5 env-highlight" | |||
const HOPP_ENV_HIGHLIGHT_FOUND = | const HOPP_ENV_HIGHLIGHT_FOUND = | |||
"bg-accentDark text-accentContrast hover:bg-accent" | "bg-accentDark text-accentContrast hover:bg-accent" | |||
const HOPP_ENV_HIGHLIGHT_NOT_FOUND = "bg-red-400 text-red-50 hover:bg-red-600" | const HOPP_ENV_HIGHLIGHT_NOT_FOUND = | |||
"bg-red-500 text-accentContrast hover:bg-red-600" | ||||
const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) => | const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) => | |||
hoverTooltip((view, pos, side) => { | hoverTooltip( | |||
const { from, to, text } = view.state.doc.lineAt(pos) | (view, pos, side) => { | |||
const { from, to, text } = view.state.doc.lineAt(pos) | ||||
// TODO: When Codemirror 6 allows this to work (not make the | ||||
// popups appear half of the time) use this implementation | // TODO: When Codemirror 6 allows this to work (not make the | |||
// const wordSelection = view.state.wordAt(pos) | // popups appear half of the time) use this implementation | |||
// if (!wordSelection) return null | // const wordSelection = view.state.wordAt(pos) | |||
// const word = view.state.doc.sliceString( | // if (!wordSelection) return null | |||
// wordSelection.from - 2, | // const word = view.state.doc.sliceString( | |||
// wordSelection.to + 2 | // wordSelection.from - 2, | |||
// ) | // wordSelection.to + 2 | |||
// if (!HOPP_ENVIRONMENT_REGEX.test(word)) return null | // ) | |||
// if (!HOPP_ENVIRONMENT_REGEX.test(word)) return null | ||||
// Tracking the start and the end of the words | ||||
let start = pos | // Tracking the start and the end of the words | |||
let end = pos | let start = pos | |||
let end = pos | ||||
while (start > from && /\w/.test(text[start - from - 1])) start-- | ||||
while (end < to && /\w/.test(text[end - from])) end++ | while (start > from && /[a-zA-Z0-9-_]+/.test(text[start - from - 1])) | |||
start-- | ||||
if ( | while (end < to && /[a-zA-Z0-9-_]+/.test(text[end - from])) end++ | |||
(start === pos && side < 0) || | ||||
(end === pos && side > 0) || | if ( | |||
!HOPP_ENVIRONMENT_REGEX.test(text.slice(start - from - 2, end - from + 2)) | (start === pos && side < 0) || | |||
) | (end === pos && side > 0) || | |||
return null | !HOPP_ENVIRONMENT_REGEX.test( | |||
text.slice(start - from - 2, end - from + 2) | ||||
const envName = | ) | |||
aggregateEnvs.find( | ) | |||
(env) => env.key === text.slice(start - from, end - from) | return null | |||
// env.key === word.slice(wordSelection.from + 2, wordSelection.to - 2) | ||||
)?.sourceEnv ?? "choose an environment" | const envName = | |||
aggregateEnvs.find( | ||||
const envValue = ( | (env) => env.key === text.slice(start - from, end - from) | |||
aggregateEnvs.find( | // env.key === word.slice(wordSelection.from + 2, wordSelection.to - 2 | |||
(env) => env.key === text.slice(start - from, end - from) | ) | |||
// env.key === word.slice(wordSelection.from + 2, wordSelection.to - 2) | )?.sourceEnv ?? "choose an environment" | |||
)?.value ?? "not found" | ||||
).replace(/"/g, """) | const envValue = | |||
aggregateEnvs.find( | ||||
const textContent = `${envName} <kbd>${envValue}</kbd>` | (env) => env.key === text.slice(start - from, end - from) | |||
// env.key === word.slice(wordSelection.from + 2, wordSelection.to - 2 | ||||
return { | ) | |||
pos: start, | )?.value ?? "not found" | |||
end: to, | ||||
above: true, | const result = parseTemplateStringE(envValue, aggregateEnvs) | |||
create() { | ||||
const dom = document.createElement("span") | const finalEnv = E.isLeft(result) ? "error" : result.right | |||
dom.innerHTML = textContent | ||||
dom.className = "tooltip-theme" | return { | |||
return { dom } | pos: start, | |||
}, | end: to, | |||
} | above: true, | |||
}) | arrow: true, | |||
create() { | ||||
const dom = document.createElement("span") | ||||
const xmp = document.createElement("xmp") | ||||
xmp.textContent = finalEnv | ||||
dom.appendChild(document.createTextNode(`${envName} `)) | ||||
dom.appendChild(xmp) | ||||
dom.className = "tippy-box" | ||||
dom.dataset.theme = "tooltip" | ||||
return { dom } | ||||
}, | ||||
} | ||||
}, | ||||
// HACK: This is a hack to fix hover tooltip not coming half of the time | ||||
// https://github.com/codemirror/tooltip/blob/765c463fc1d5afcc3ec93cee47d726 | ||||
06bed27e1d/src/tooltip.ts#L622 | ||||
// Still doesn't fix the not showing up some of the time issue, but this is | ||||
atleast more consistent | ||||
{ hoverTime: 1 } as any | ||||
) | ||||
function checkEnv(env: string, aggregateEnvs: AggregateEnvironment[]) { | function checkEnv(env: string, aggregateEnvs: AggregateEnvironment[]) { | |||
const className = aggregateEnvs.find( | const className = aggregateEnvs.find( | |||
(k: { key: string }) => k.key === env.slice(2, -2) | (k: { key: string }) => k.key === env.slice(2, -2) | |||
) | ) | |||
? HOPP_ENV_HIGHLIGHT_FOUND | ? HOPP_ENV_HIGHLIGHT_FOUND | |||
: HOPP_ENV_HIGHLIGHT_NOT_FOUND | : HOPP_ENV_HIGHLIGHT_NOT_FOUND | |||
return Decoration.mark({ | return Decoration.mark({ | |||
class: `${HOPP_ENV_HIGHLIGHT} ${className}`, | class: `${HOPP_ENV_HIGHLIGHT} ${className}`, | |||
skipping to change at line 147 | skipping to change at line 165 | |||
}) | }) | |||
} | } | |||
get extension() { | get extension() { | |||
return this.compartment.of([ | return this.compartment.of([ | |||
cursorTooltipField(this.envs), | cursorTooltipField(this.envs), | |||
environmentHighlightStyle(this.envs), | environmentHighlightStyle(this.envs), | |||
]) | ]) | |||
} | } | |||
} | } | |||
export class HoppReactiveEnvPlugin { | ||||
private compartment = new Compartment() | ||||
private envs: AggregateEnvironment[] = [] | ||||
constructor( | ||||
envsRef: Ref<AggregateEnvironment[]>, | ||||
private editorView: Ref<EditorView | undefined> | ||||
) { | ||||
watch( | ||||
envsRef, | ||||
(envs) => { | ||||
this.envs = envs | ||||
this.editorView.value?.dispatch({ | ||||
effects: this.compartment.reconfigure([ | ||||
cursorTooltipField(this.envs), | ||||
environmentHighlightStyle(this.envs), | ||||
]), | ||||
}) | ||||
}, | ||||
{ immediate: true } | ||||
) | ||||
} | ||||
get extension() { | ||||
return this.compartment.of([ | ||||
cursorTooltipField(this.envs), | ||||
environmentHighlightStyle(this.envs), | ||||
]) | ||||
} | ||||
} | ||||
End of changes. 9 change blocks. | ||||
60 lines changed or deleted | 82 lines changed or added |