chore: split app to commons and web (squash commit)
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import { Ref } from "vue"
|
||||
import { GraphQLSchema } from "graphql"
|
||||
import { getAutocompleteSuggestions } from "graphql-language-service-interface"
|
||||
import { Completer, CompleterResult, CompletionEntry } from "."
|
||||
|
||||
const completer: (schemaRef: Ref<GraphQLSchema | null>) => Completer =
|
||||
(schemaRef: Ref<GraphQLSchema | null>) => (text, completePos) => {
|
||||
if (!schemaRef.value) return Promise.resolve(null)
|
||||
|
||||
const completions = getAutocompleteSuggestions(schemaRef.value, text, {
|
||||
line: completePos.line,
|
||||
character: completePos.ch,
|
||||
} as any)
|
||||
|
||||
return Promise.resolve(<CompleterResult>{
|
||||
completions: completions.map(
|
||||
(x, i) =>
|
||||
<CompletionEntry>{
|
||||
text: x.label!,
|
||||
meta: x.detail!,
|
||||
score: completions.length - i,
|
||||
}
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
export default completer
|
||||
@@ -0,0 +1,23 @@
|
||||
export type CompletionEntry = {
|
||||
text: string
|
||||
meta: string
|
||||
score: number
|
||||
}
|
||||
|
||||
export type CompleterResult = {
|
||||
/**
|
||||
* List of completions to display
|
||||
*/
|
||||
completions: CompletionEntry[]
|
||||
}
|
||||
|
||||
export type Completer = (
|
||||
/**
|
||||
* The contents of the editor
|
||||
*/
|
||||
text: string,
|
||||
/**
|
||||
* Position where the completer is fired
|
||||
*/
|
||||
completePos: { line: number; ch: number }
|
||||
) => Promise<CompleterResult | null>
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Completer, CompletionEntry } from "."
|
||||
import { getPreRequestScriptCompletions } from "~/helpers/tern"
|
||||
|
||||
const completer: Completer = async (text, completePos) => {
|
||||
const results = await getPreRequestScriptCompletions(
|
||||
text,
|
||||
completePos.line,
|
||||
completePos.ch
|
||||
)
|
||||
|
||||
const completions = results.completions.map((completion: any, i: number) => {
|
||||
return <CompletionEntry>{
|
||||
text: completion.name,
|
||||
meta: completion.isKeyword ? "keyword" : completion.type,
|
||||
score: results.completions.length - i,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
completions,
|
||||
}
|
||||
}
|
||||
|
||||
export default completer
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Completer, CompletionEntry } from "."
|
||||
import { getTestScriptCompletions } from "~/helpers/tern"
|
||||
|
||||
export const completer: Completer = async (text, completePos) => {
|
||||
const results = await getTestScriptCompletions(
|
||||
text,
|
||||
completePos.line,
|
||||
completePos.ch
|
||||
)
|
||||
|
||||
const completions = results.completions.map((completion: any, i: number) => {
|
||||
return <CompletionEntry>{
|
||||
text: completion.name,
|
||||
meta: completion.isKeyword ? "keyword" : completion.type,
|
||||
score: results.completions.length - i,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
completions,
|
||||
}
|
||||
}
|
||||
|
||||
export default completer
|
||||
@@ -0,0 +1,226 @@
|
||||
import { watch, Ref } from "vue"
|
||||
import { Compartment } from "@codemirror/state"
|
||||
import {
|
||||
Decoration,
|
||||
EditorView,
|
||||
MatchDecorator,
|
||||
ViewPlugin,
|
||||
hoverTooltip,
|
||||
} from "@codemirror/view"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { parseTemplateStringE } from "@hoppscotch/data"
|
||||
import { StreamSubscriberFunc } from "@composables/stream"
|
||||
import {
|
||||
AggregateEnvironment,
|
||||
aggregateEnvs$,
|
||||
getAggregateEnvs,
|
||||
getSelectedEnvironmentType,
|
||||
} from "~/newstore/environments"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
|
||||
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
|
||||
|
||||
const HOPP_ENV_HIGHLIGHT =
|
||||
"cursor-help transition rounded px-1 focus:outline-none mx-0.5 env-highlight"
|
||||
const HOPP_ENV_HIGHLIGHT_FOUND =
|
||||
"bg-accentDark text-accentContrast hover:bg-accent"
|
||||
const HOPP_ENV_HIGHLIGHT_NOT_FOUND =
|
||||
"bg-red-500 text-accentContrast hover:bg-red-600"
|
||||
|
||||
const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
|
||||
hoverTooltip(
|
||||
(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
|
||||
// const wordSelection = view.state.wordAt(pos)
|
||||
// if (!wordSelection) return null
|
||||
// const word = view.state.doc.sliceString(
|
||||
// wordSelection.from - 2,
|
||||
// wordSelection.to + 2
|
||||
// )
|
||||
// if (!HOPP_ENVIRONMENT_REGEX.test(word)) return null
|
||||
|
||||
// Tracking the start and the end of the words
|
||||
let start = pos
|
||||
let end = pos
|
||||
|
||||
while (start > from && /[a-zA-Z0-9-_]+/.test(text[start - from - 1]))
|
||||
start--
|
||||
while (end < to && /[a-zA-Z0-9-_]+/.test(text[end - from])) end++
|
||||
|
||||
if (
|
||||
(start === pos && side < 0) ||
|
||||
(end === pos && side > 0) ||
|
||||
!HOPP_ENVIRONMENT_REGEX.test(
|
||||
text.slice(start - from - 2, end - from + 2)
|
||||
)
|
||||
)
|
||||
return null
|
||||
|
||||
const parsedEnvKey = text.slice(start - from, end - from)
|
||||
|
||||
const tooltipEnv = aggregateEnvs.find((env) => env.key === parsedEnvKey)
|
||||
|
||||
const envName = tooltipEnv?.sourceEnv ?? "Choose an Environment"
|
||||
|
||||
const envValue = tooltipEnv?.value ?? "Not found"
|
||||
|
||||
const result = parseTemplateStringE(envValue, aggregateEnvs)
|
||||
|
||||
const finalEnv = E.isLeft(result) ? "error" : result.right
|
||||
|
||||
const selectedEnvType = getSelectedEnvironmentType()
|
||||
|
||||
const envTypeIcon = `<i class="inline-flex -my-1 -mx-0.5 opacity-65 items-center text-base material-icons border-secondary">${
|
||||
selectedEnvType === "TEAM_ENV" ? "people" : "person"
|
||||
}</i>`
|
||||
|
||||
const appendEditAction = (tooltip: HTMLElement) => {
|
||||
const editIcon = document.createElement("span")
|
||||
editIcon.className =
|
||||
"ml-2 cursor-pointer env-icon text-accent hover:text-accentDark"
|
||||
editIcon.addEventListener("click", () => {
|
||||
const isPersonalEnv =
|
||||
envName === "Global" || selectedEnvType !== "TEAM_ENV"
|
||||
const action = isPersonalEnv ? "my" : "team"
|
||||
invokeAction(`modals.${action}.environment.edit`, {
|
||||
envName,
|
||||
variableName: parsedEnvKey,
|
||||
})
|
||||
})
|
||||
editIcon.innerHTML = `<i class="inline-flex items-center px-1 -mx-1 -my-1 text-base material-icons border-secondary">drive_file_rename_outline</i>`
|
||||
tooltip.appendChild(editIcon)
|
||||
}
|
||||
|
||||
return {
|
||||
pos: start,
|
||||
end: to,
|
||||
above: true,
|
||||
arrow: true,
|
||||
create() {
|
||||
const dom = document.createElement("span")
|
||||
const tooltipContainer = document.createElement("span")
|
||||
const kbd = document.createElement("kbd")
|
||||
const icon = document.createElement("span")
|
||||
icon.innerHTML = envTypeIcon
|
||||
icon.className = "mr-2 env-icon"
|
||||
kbd.textContent = finalEnv
|
||||
tooltipContainer.appendChild(icon)
|
||||
tooltipContainer.appendChild(document.createTextNode(`${envName} `))
|
||||
tooltipContainer.appendChild(kbd)
|
||||
if (tooltipEnv) appendEditAction(tooltipContainer)
|
||||
tooltipContainer.className = "tippy-content"
|
||||
dom.className = "tippy-box"
|
||||
dom.dataset.theme = "tooltip"
|
||||
dom.appendChild(tooltipContainer)
|
||||
return { dom }
|
||||
},
|
||||
}
|
||||
},
|
||||
// HACK: This is a hack to fix hover tooltip not coming half of the time
|
||||
// https://github.com/codemirror/tooltip/blob/765c463fc1d5afcc3ec93cee47d72606bed27e1d/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[]) {
|
||||
const className = aggregateEnvs.find(
|
||||
(k: { key: string }) => k.key === env.slice(2, -2)
|
||||
)
|
||||
? HOPP_ENV_HIGHLIGHT_FOUND
|
||||
: HOPP_ENV_HIGHLIGHT_NOT_FOUND
|
||||
|
||||
return Decoration.mark({
|
||||
class: `${HOPP_ENV_HIGHLIGHT} ${className}`,
|
||||
})
|
||||
}
|
||||
|
||||
const getMatchDecorator = (aggregateEnvs: AggregateEnvironment[]) =>
|
||||
new MatchDecorator({
|
||||
regexp: HOPP_ENVIRONMENT_REGEX,
|
||||
decoration: (m) => checkEnv(m[0], aggregateEnvs),
|
||||
})
|
||||
|
||||
export const environmentHighlightStyle = (
|
||||
aggregateEnvs: AggregateEnvironment[]
|
||||
) => {
|
||||
const decorator = getMatchDecorator(aggregateEnvs)
|
||||
|
||||
return ViewPlugin.define(
|
||||
(view) => ({
|
||||
decorations: decorator.createDeco(view),
|
||||
update(u) {
|
||||
this.decorations = decorator.updateDeco(u, this.decorations)
|
||||
},
|
||||
}),
|
||||
{
|
||||
decorations: (v) => v.decorations,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export class HoppEnvironmentPlugin {
|
||||
private compartment = new Compartment()
|
||||
|
||||
private envs: AggregateEnvironment[] = []
|
||||
|
||||
constructor(
|
||||
subscribeToStream: StreamSubscriberFunc,
|
||||
private editorView: Ref<EditorView | undefined>
|
||||
) {
|
||||
this.envs = getAggregateEnvs()
|
||||
|
||||
subscribeToStream(aggregateEnvs$, (envs) => {
|
||||
this.envs = envs
|
||||
|
||||
this.editorView.value?.dispatch({
|
||||
effects: this.compartment.reconfigure([
|
||||
cursorTooltipField(this.envs),
|
||||
environmentHighlightStyle(this.envs),
|
||||
]),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
get extension() {
|
||||
return this.compartment.of([
|
||||
cursorTooltipField(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),
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Ref } from "vue"
|
||||
import {
|
||||
GraphQLError,
|
||||
GraphQLSchema,
|
||||
parse as gqlParse,
|
||||
validate as gqlValidate,
|
||||
} from "graphql"
|
||||
import { LinterDefinition, LinterResult } from "./linter"
|
||||
|
||||
/**
|
||||
* Creates a Linter function that can lint a GQL query against a given
|
||||
* schema
|
||||
*/
|
||||
export const createGQLQueryLinter: (
|
||||
schema: Ref<GraphQLSchema | null>
|
||||
) => LinterDefinition = (schema: Ref<GraphQLSchema | null>) => (text) => {
|
||||
if (text === "") return Promise.resolve([])
|
||||
if (!schema.value) return Promise.resolve([])
|
||||
|
||||
try {
|
||||
const doc = gqlParse(text)
|
||||
|
||||
const results = gqlValidate(schema.value, doc).map(
|
||||
({ locations, message }) =>
|
||||
<LinterResult>{
|
||||
from: {
|
||||
line: locations![0].line,
|
||||
ch: locations![0].column - 1,
|
||||
},
|
||||
to: {
|
||||
line: locations![0].line,
|
||||
ch: locations![0].column - 1,
|
||||
},
|
||||
message,
|
||||
severity: "error",
|
||||
}
|
||||
)
|
||||
|
||||
return Promise.resolve(results)
|
||||
} catch (e) {
|
||||
const err = e as GraphQLError
|
||||
|
||||
return Promise.resolve([
|
||||
<LinterResult>{
|
||||
from: {
|
||||
line: err.locations![0].line,
|
||||
ch: err.locations![0].column - 1,
|
||||
},
|
||||
to: {
|
||||
line: err.locations![0].line,
|
||||
ch: err.locations![0].column,
|
||||
},
|
||||
message: err.message,
|
||||
severity: "error",
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { convertIndexToLineCh } from "../utils"
|
||||
import { LinterDefinition, LinterResult } from "./linter"
|
||||
import jsonParse from "~/helpers/jsonParse"
|
||||
|
||||
const linter: LinterDefinition = (text) => {
|
||||
try {
|
||||
jsonParse(text)
|
||||
return Promise.resolve([])
|
||||
} catch (e: any) {
|
||||
return Promise.resolve([
|
||||
<LinterResult>{
|
||||
from: convertIndexToLineCh(text, e.start),
|
||||
to: convertIndexToLineCh(text, e.end),
|
||||
message: e.message,
|
||||
severity: "error",
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
export default linter
|
||||
@@ -0,0 +1,7 @@
|
||||
export type LinterResult = {
|
||||
message: string
|
||||
severity: "warning" | "error"
|
||||
from: { line: number; ch: number }
|
||||
to: { line: number; ch: number }
|
||||
}
|
||||
export type LinterDefinition = (text: string) => Promise<LinterResult[]>
|
||||
@@ -0,0 +1,75 @@
|
||||
import * as esprima from "esprima"
|
||||
import { LinterDefinition, LinterResult } from "./linter"
|
||||
import { performPreRequestLinting } from "~/helpers/tern"
|
||||
|
||||
const linter: LinterDefinition = async (text) => {
|
||||
let results: LinterResult[] = []
|
||||
|
||||
// Semantic linting
|
||||
const semanticLints = await performPreRequestLinting(text)
|
||||
|
||||
results = results.concat(
|
||||
semanticLints.map((lint: any) => ({
|
||||
from: {
|
||||
ch: lint.from.ch + 1,
|
||||
line: lint.from.line + 1,
|
||||
},
|
||||
to: {
|
||||
ch: lint.from.ch + 1,
|
||||
line: lint.to.line + 1,
|
||||
},
|
||||
severity: "error",
|
||||
message: `[semantic] ${lint.message}`,
|
||||
}))
|
||||
)
|
||||
|
||||
// Syntax linting
|
||||
try {
|
||||
const res: any = esprima.parseScript(text, { tolerant: true })
|
||||
if (res.errors && res.errors.length > 0) {
|
||||
results = results.concat(
|
||||
res.errors.map((err: any) => {
|
||||
const fromPos: { line: number; ch: number } = {
|
||||
line: err.lineNumber,
|
||||
ch: err.column,
|
||||
}
|
||||
|
||||
const toPos: { line: number; ch: number } = {
|
||||
line: err.lineNumber,
|
||||
ch: err.column,
|
||||
}
|
||||
|
||||
return <LinterResult>{
|
||||
from: fromPos,
|
||||
to: toPos,
|
||||
message: `[syntax] ${err.description}`,
|
||||
severity: "error",
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
} catch (e: any) {
|
||||
const fromPos: { line: number; ch: number } = {
|
||||
line: e.lineNumber,
|
||||
ch: e.column,
|
||||
}
|
||||
|
||||
const toPos: { line: number; ch: number } = {
|
||||
line: e.lineNumber,
|
||||
ch: e.column,
|
||||
}
|
||||
|
||||
results = results.concat([
|
||||
<LinterResult>{
|
||||
from: fromPos,
|
||||
to: toPos,
|
||||
message: `[syntax] ${e.description}`,
|
||||
severity: "error",
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export default linter
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as E from "fp-ts/Either"
|
||||
import { strictParseRawKeyValueEntriesE } from "@hoppscotch/data"
|
||||
import { convertIndexToLineCh } from "../utils"
|
||||
import { LinterDefinition, LinterResult } from "./linter"
|
||||
|
||||
const linter: LinterDefinition = (text) => {
|
||||
const result = strictParseRawKeyValueEntriesE(text)
|
||||
if (E.isLeft(result)) {
|
||||
const pos = convertIndexToLineCh(text, result.left.pos)
|
||||
|
||||
return Promise.resolve([
|
||||
<LinterResult>{
|
||||
from: pos,
|
||||
to: pos,
|
||||
message: result.left.message,
|
||||
severity: "error",
|
||||
},
|
||||
])
|
||||
} else {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
}
|
||||
|
||||
export default linter
|
||||
@@ -0,0 +1,75 @@
|
||||
import * as esprima from "esprima"
|
||||
import { LinterDefinition, LinterResult } from "./linter"
|
||||
import { performTestLinting } from "~/helpers/tern"
|
||||
|
||||
const linter: LinterDefinition = async (text) => {
|
||||
let results: LinterResult[] = []
|
||||
|
||||
// Semantic linting
|
||||
const semanticLints = await performTestLinting(text)
|
||||
|
||||
results = results.concat(
|
||||
semanticLints.map((lint: any) => ({
|
||||
from: {
|
||||
ch: lint.from.ch + 1,
|
||||
line: lint.from.line + 1,
|
||||
},
|
||||
to: {
|
||||
ch: lint.from.ch + 1,
|
||||
line: lint.to.line + 1,
|
||||
},
|
||||
severity: "error",
|
||||
message: `[semantic] ${lint.message}`,
|
||||
}))
|
||||
)
|
||||
|
||||
// Syntax linting
|
||||
try {
|
||||
const res: any = esprima.parseScript(text, { tolerant: true })
|
||||
if (res.errors && res.errors.length > 0) {
|
||||
results = results.concat(
|
||||
res.errors.map((err: any) => {
|
||||
const fromPos: { line: number; ch: number } = {
|
||||
line: err.lineNumber,
|
||||
ch: err.column,
|
||||
}
|
||||
|
||||
const toPos: { line: number; ch: number } = {
|
||||
line: err.lineNumber,
|
||||
ch: err.column,
|
||||
}
|
||||
|
||||
return <LinterResult>{
|
||||
from: fromPos,
|
||||
to: toPos,
|
||||
message: `[syntax] ${err.description}`,
|
||||
severity: "error",
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
} catch (e: any) {
|
||||
const fromPos: { line: number; ch: number } = {
|
||||
line: e.lineNumber,
|
||||
ch: e.column,
|
||||
}
|
||||
|
||||
const toPos: { line: number; ch: number } = {
|
||||
line: e.lineNumber,
|
||||
ch: e.column,
|
||||
}
|
||||
|
||||
results = results.concat([
|
||||
<LinterResult>{
|
||||
from: fromPos,
|
||||
to: toPos,
|
||||
message: `[syntax] ${e.description}`,
|
||||
severity: "error",
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export default linter
|
||||
@@ -0,0 +1,413 @@
|
||||
import {
|
||||
EditorView,
|
||||
keymap,
|
||||
highlightSpecialChars,
|
||||
highlightActiveLine,
|
||||
drawSelection,
|
||||
dropCursor,
|
||||
lineNumbers,
|
||||
highlightActiveLineGutter,
|
||||
rectangularSelection,
|
||||
crosshairCursor,
|
||||
} from "@codemirror/view"
|
||||
import {
|
||||
HighlightStyle,
|
||||
defaultHighlightStyle,
|
||||
foldKeymap,
|
||||
foldGutter,
|
||||
indentOnInput,
|
||||
bracketMatching,
|
||||
syntaxHighlighting,
|
||||
} from "@codemirror/language"
|
||||
import { tags as t } from "@lezer/highlight"
|
||||
import { Extension, EditorState } from "@codemirror/state"
|
||||
import { history, historyKeymap, defaultKeymap } from "@codemirror/commands"
|
||||
import {
|
||||
closeBrackets,
|
||||
closeBracketsKeymap,
|
||||
autocompletion,
|
||||
completionKeymap,
|
||||
} from "@codemirror/autocomplete"
|
||||
import {
|
||||
searchKeymap,
|
||||
highlightSelectionMatches,
|
||||
search,
|
||||
} from "@codemirror/search"
|
||||
import { lintKeymap } from "@codemirror/lint"
|
||||
|
||||
export const baseTheme = EditorView.theme({
|
||||
"&": {
|
||||
fontSize: "var(--font-size-body)",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
flex: "1",
|
||||
},
|
||||
".cm-content": {
|
||||
caretColor: "var(--secondary-dark-color)",
|
||||
fontFamily: "var(--font-mono)",
|
||||
color: "var(--secondary-dark-color)",
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
".cm-cursor": {
|
||||
borderColor: "var(--secondary-color)",
|
||||
},
|
||||
".cm-widgetBuffer": {
|
||||
position: "absolute",
|
||||
},
|
||||
".cm-selectionBackground": {
|
||||
backgroundColor: "var(--accent-dark-color)",
|
||||
color: "var(--accent-contrast-color)",
|
||||
borderRadius: "2px",
|
||||
},
|
||||
".cm-panels": {
|
||||
backgroundColor: "var(--primary-light-color)",
|
||||
color: "var(--secondary-light-color)",
|
||||
},
|
||||
".cm-panels.cm-panels-top": {
|
||||
borderBottom: "1px solid var(--divider-light-color)",
|
||||
},
|
||||
".cm-panels.cm-panels-bottom": {
|
||||
borderTop: "1px solid var(--divider-light-color)",
|
||||
},
|
||||
".cm-search": {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexWrap: "nowrap",
|
||||
flexShrink: 0,
|
||||
overflow: "auto",
|
||||
},
|
||||
".cm-search label": {
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
".cm-textfield": {
|
||||
backgroundColor: "var(--primary-dark-color)",
|
||||
color: "var(--secondary-dark-color)",
|
||||
borderColor: "var(--divider-light-color)",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
".cm-button": {
|
||||
backgroundColor: "var(--primary-dark-color)",
|
||||
color: "var(--secondary-dark-color)",
|
||||
backgroundImage: "none",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
".cm-completionLabel": {
|
||||
color: "var(--secondary-color)",
|
||||
},
|
||||
".cm-tooltip": {
|
||||
backgroundColor: "var(--primary-dark-color)",
|
||||
color: "var(--secondary-light-color)",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
".cm-tooltip-arrow": {
|
||||
color: "var(--tooltip-color)",
|
||||
},
|
||||
".cm-tooltip-arrow:after": {
|
||||
borderTopColor: "inherit !important",
|
||||
},
|
||||
".cm-tooltip-arrow:before": {
|
||||
borderTopColor: "inherit !important",
|
||||
},
|
||||
".cm-tooltip.cm-tooltip-autocomplete > ul": {
|
||||
fontFamily: "var(--font-mono)",
|
||||
},
|
||||
".cm-tooltip-autocomplete ul li[aria-selected]": {
|
||||
backgroundColor: "var(--accent-dark-color)",
|
||||
color: "var(--accent-contrast-color)",
|
||||
},
|
||||
".cm-tooltip-autocomplete ul li[aria-selected] .cm-completionLabel": {
|
||||
color: "var(--accent-contrast-color)",
|
||||
},
|
||||
".cm-activeLine": { backgroundColor: "transparent" },
|
||||
".cm-searchMatch": {
|
||||
outline: "1px solid var(--accent-dark-color)",
|
||||
backgroundColor: "var(--divider-dark-color)",
|
||||
borderRadius: "2px",
|
||||
},
|
||||
".cm-selectionMatch": {
|
||||
outline: "1px solid var(--accent-dark-color)",
|
||||
backgroundColor: "var(--divider-light-color)",
|
||||
borderRadius: "2px",
|
||||
},
|
||||
".cm-matchingBracket, .cm-nonmatchingBracket": {
|
||||
backgroundColor: "var(--divider-color)",
|
||||
outline: "1px solid var(--accent-dark-color)",
|
||||
borderRadius: "2px",
|
||||
},
|
||||
".cm-gutters": {
|
||||
fontFamily: "var(--font-mono)",
|
||||
backgroundColor: "var(--primary-color)",
|
||||
borderColor: "var(--divider-light-color)",
|
||||
},
|
||||
".cm-lineNumbers": {
|
||||
minWidth: "3em",
|
||||
color: "var(--secondary-light-color)",
|
||||
},
|
||||
".cm-foldGutter": {
|
||||
minWidth: "2em",
|
||||
color: "var(--secondary-light-color)",
|
||||
},
|
||||
".cm-foldGutter .cm-gutterElement": {
|
||||
textAlign: "center",
|
||||
},
|
||||
".cm-line": {
|
||||
paddingLeft: "0.5em",
|
||||
paddingRight: "0.5em",
|
||||
},
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar": {
|
||||
display: "none",
|
||||
},
|
||||
".cm-foldPlaceholder": {
|
||||
backgroundColor: "var(--divider-light-color)",
|
||||
color: "var(--secondary-dark-color)",
|
||||
borderColor: "var(--divider-dark-color)",
|
||||
},
|
||||
})
|
||||
|
||||
export const inputTheme = EditorView.theme({
|
||||
"&": {
|
||||
fontSize: "var(--font-size-body)",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
flex: "1",
|
||||
},
|
||||
".cm-content": {
|
||||
caretColor: "var(--secondary-dark-color)",
|
||||
fontFamily: "var(--font-sans)",
|
||||
color: "var(--secondary-dark-color)",
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
".cm-cursor": {
|
||||
borderColor: "var(--secondary-color)",
|
||||
},
|
||||
".cm-widgetBuffer": {
|
||||
position: "absolute",
|
||||
},
|
||||
".cm-selectionBackground": {
|
||||
backgroundColor: "var(--accent-dark-color)",
|
||||
color: "var(--accent-contrast-color)",
|
||||
borderRadius: "2px",
|
||||
},
|
||||
".cm-panels": {
|
||||
backgroundColor: "var(--primary-light-color)",
|
||||
color: "var(--secondary-light-color)",
|
||||
},
|
||||
".cm-panels.cm-panels-top": {
|
||||
borderBottom: "1px solid var(--divider-light-color)",
|
||||
},
|
||||
".cm-panels.cm-panels-bottom": {
|
||||
borderTop: "1px solid var(--divider-light-color)",
|
||||
},
|
||||
".cm-search": {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexWrap: "nowrap",
|
||||
flexShrink: 0,
|
||||
overflow: "auto",
|
||||
},
|
||||
".cm-search label": {
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
".cm-textfield": {
|
||||
backgroundColor: "var(--primary-dark-color)",
|
||||
color: "var(--secondary-dark-color)",
|
||||
borderColor: "var(--divider-light-color)",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
".cm-button": {
|
||||
backgroundColor: "var(--primary-dark-color)",
|
||||
color: "var(--secondary-dark-color)",
|
||||
backgroundImage: "none",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
".cm-completionLabel": {
|
||||
color: "var(--secondary-color)",
|
||||
},
|
||||
".cm-tooltip": {
|
||||
backgroundColor: "var(--primary-dark-color)",
|
||||
color: "var(--secondary-light-color)",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
".cm-tooltip-arrow": {
|
||||
color: "var(--tooltip-color)",
|
||||
},
|
||||
".cm-tooltip-arrow:after": {
|
||||
borderTopColor: "currentColor !important",
|
||||
},
|
||||
".cm-tooltip-arrow:before": {
|
||||
borderTopColor: "currentColor !important",
|
||||
},
|
||||
".cm-tooltip.cm-tooltip-autocomplete > ul": {
|
||||
fontFamily: "var(--font-mono)",
|
||||
},
|
||||
".cm-tooltip-autocomplete ul li[aria-selected]": {
|
||||
backgroundColor: "var(--accent-dark-color)",
|
||||
color: "var(--accent-contrast-color)",
|
||||
},
|
||||
".cm-tooltip-autocomplete ul li[aria-selected] .cm-completionLabel": {
|
||||
color: "var(--accent-contrast-color)",
|
||||
},
|
||||
".cm-activeLine": { backgroundColor: "transparent" },
|
||||
".cm-searchMatch": {
|
||||
outline: "1px solid var(--accent-dark-color)",
|
||||
backgroundColor: "var(--divider-dark-color)",
|
||||
borderRadius: "2px",
|
||||
},
|
||||
".cm-selectionMatch": {
|
||||
outline: "1px solid var(--accent-dark-color)",
|
||||
backgroundColor: "var(--divider-light-color)",
|
||||
borderRadius: "2px",
|
||||
},
|
||||
".cm-matchingBracket, .cm-nonmatchingBracket": {
|
||||
backgroundColor: "var(--divider-color)",
|
||||
outline: "1px solid var(--accent-dark-color)",
|
||||
borderRadius: "2px",
|
||||
},
|
||||
".cm-gutters": {
|
||||
fontFamily: "var(--font-mono)",
|
||||
backgroundColor: "var(--primary-color)",
|
||||
borderColor: "var(--divider-light-color)",
|
||||
},
|
||||
".cm-lineNumbers": {
|
||||
minWidth: "3em",
|
||||
color: "var(--secondary-light-color)",
|
||||
},
|
||||
".cm-foldGutter": {
|
||||
minWidth: "2em",
|
||||
color: "var(--secondary-light-color)",
|
||||
},
|
||||
".cm-foldGutter .cm-gutterElement": {
|
||||
textAlign: "center",
|
||||
},
|
||||
".cm-line": {
|
||||
paddingLeft: "1rem",
|
||||
paddingRight: "1rem",
|
||||
paddingTop: "0.2rem",
|
||||
paddingBottom: "0.2rem",
|
||||
},
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar": {
|
||||
display: "none",
|
||||
},
|
||||
".cm-foldPlaceholder": {
|
||||
backgroundColor: "var(--divider-light-color)",
|
||||
color: "var(--secondary-dark-color)",
|
||||
borderColor: "var(--divider-dark-color)",
|
||||
},
|
||||
})
|
||||
|
||||
const editorTypeColor = "var(--editor-type-color)"
|
||||
const editorNameColor = "var(--editor-name-color)"
|
||||
const editorOperatorColor = "var(--editor-operator-color)"
|
||||
const editorInvalidColor = "var(--editor-invalid-color)"
|
||||
const editorSeparatorColor = "var(--editor-separator-color)"
|
||||
const editorMetaColor = "var(--editor-meta-color)"
|
||||
const editorVariableColor = "var(--editor-variable-color)"
|
||||
const editorLinkColor = "var(--editor-link-color)"
|
||||
const editorProcessColor = "var(--editor-process-color)"
|
||||
const editorConstantColor = "var(--editor-constant-color)"
|
||||
const editorKeywordColor = "var(--editor-keyword-color)"
|
||||
|
||||
export const baseHighlightStyle = HighlightStyle.define([
|
||||
{ tag: t.keyword, color: editorKeywordColor },
|
||||
{
|
||||
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
|
||||
color: editorNameColor,
|
||||
},
|
||||
{
|
||||
tag: [t.function(t.variableName), t.labelName],
|
||||
color: editorVariableColor,
|
||||
},
|
||||
{
|
||||
tag: [t.color, t.constant(t.name), t.standard(t.name)],
|
||||
color: editorConstantColor,
|
||||
},
|
||||
{ tag: [t.definition(t.name), t.separator], color: editorSeparatorColor },
|
||||
{
|
||||
tag: [
|
||||
t.typeName,
|
||||
t.className,
|
||||
t.number,
|
||||
t.changed,
|
||||
t.annotation,
|
||||
t.modifier,
|
||||
t.self,
|
||||
t.namespace,
|
||||
],
|
||||
color: editorTypeColor,
|
||||
},
|
||||
{
|
||||
tag: [
|
||||
t.operator,
|
||||
t.operatorKeyword,
|
||||
t.url,
|
||||
t.escape,
|
||||
t.regexp,
|
||||
t.link,
|
||||
t.special(t.string),
|
||||
],
|
||||
color: editorOperatorColor,
|
||||
},
|
||||
{ tag: [t.meta, t.comment], color: editorMetaColor },
|
||||
{ tag: t.strong, fontWeight: "bold" },
|
||||
{ tag: t.emphasis, fontStyle: "italic" },
|
||||
{ tag: t.strikethrough, textDecoration: "line-through" },
|
||||
{ tag: t.link, color: editorLinkColor, textDecoration: "underline" },
|
||||
{ tag: t.heading, fontWeight: "bold", color: editorNameColor },
|
||||
{
|
||||
tag: [t.atom, t.bool, t.special(t.variableName)],
|
||||
color: editorConstantColor,
|
||||
},
|
||||
{
|
||||
tag: [t.processingInstruction, t.string, t.inserted],
|
||||
color: editorProcessColor,
|
||||
},
|
||||
{ tag: t.invalid, color: editorInvalidColor },
|
||||
])
|
||||
|
||||
export const basicSetup: Extension = [
|
||||
lineNumbers(),
|
||||
highlightActiveLineGutter(),
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
foldGutter({
|
||||
openText: "▾",
|
||||
closedText: "▸",
|
||||
}),
|
||||
drawSelection(),
|
||||
dropCursor(),
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
indentOnInput(),
|
||||
syntaxHighlighting(baseHighlightStyle),
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
autocompletion(),
|
||||
rectangularSelection(),
|
||||
crosshairCursor(),
|
||||
highlightActiveLine(),
|
||||
highlightSelectionMatches(),
|
||||
keymap.of([
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
...searchKeymap,
|
||||
...historyKeymap,
|
||||
...foldKeymap,
|
||||
...completionKeymap,
|
||||
...lintKeymap,
|
||||
]),
|
||||
search({
|
||||
top: true,
|
||||
}),
|
||||
]
|
||||
38
packages/hoppscotch-common/src/helpers/editor/utils.ts
Normal file
38
packages/hoppscotch-common/src/helpers/editor/utils.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export function convertIndexToLineCh(
|
||||
text: string,
|
||||
i: number
|
||||
): { line: number; ch: number } {
|
||||
const lines = text.split("\n")
|
||||
|
||||
let line = 0
|
||||
let counter = 0
|
||||
|
||||
while (line < lines.length) {
|
||||
if (i > lines[line].length + counter) {
|
||||
counter += lines[line].length + 1
|
||||
line++
|
||||
} else {
|
||||
return {
|
||||
line: line + 1,
|
||||
ch: i - counter + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Invalid input")
|
||||
}
|
||||
|
||||
export function convertLineChToIndex(
|
||||
text: string,
|
||||
lineCh: { line: number; ch: number }
|
||||
): number {
|
||||
const textSplit = text.split("\n")
|
||||
|
||||
if (textSplit.length < lineCh.line) throw new Error("Invalid position")
|
||||
|
||||
const tillLineIndex = textSplit
|
||||
.slice(0, lineCh.line)
|
||||
.reduce((acc, line) => acc + line.length + 1, 0)
|
||||
|
||||
return tillLineIndex + lineCh.ch
|
||||
}
|
||||
Reference in New Issue
Block a user