/** * GraphQL in editors, after IDEA's GraphQL plugin (JetBrains/js-graphql-intellij-plugin): * - the toolbar above GraphQL files: Open Configuration File, Edit Environment Variables, * Toggle Variables Editor, the endpoint, Execute GraphQL, Rerun Introspection Query and * Open Introspection Schema; the variables editor (JSON) shows below it; * - schema files re-checked as they are edited (unresolved types); * - in graphql-config files, ▶ on each endpoint runs its introspection query. */ import { RangeSet, StateEffect, StateField, type EditorState, type Extension } from "@codemirror/state"; import { Decoration, EditorView, GutterMarker, ViewPlugin, gutter, showPanel, type DecorationSet, type Panel, type ViewUpdate, } from "@codemirror/view"; import { useEffect, useRef } from "react"; import { createRoot } from "react-dom/client"; import type { EditorDocContext } from "@/editor/api"; import { highlightInto } from "@/editor/lsp/markdown"; import { useTx } from "@/platform/i18n"; import { shortcutLabel } from "@/platform/keymap"; import { basename } from "@/platform/project"; import { isVirtualUri } from "@/platform/virtualDocs"; import { IconButton, Select, gutterIconMarkup } from "@/ui"; import { endpointLines, isConfigFile, outsideRanges, queryContext } from "./model"; import { chooseEndpoint, configurationFor, discoverSchemasNow, editEnvironmentVariables, endpointFor, introspectEndpoint, openConfiguration, openIntrospectionSchema, rerunIntrospection, revalidateFile, runQuery, schemaOf, setVariables, toggleVariables, useGraphQL, } from "./store"; import styles from "./GraphQL.module.css"; export const GRAPHQL_LANGUAGE = "graphql"; // --- query context: what Execute GraphQL sent, the rest greyed out (IDEA) --------------------- const setSentRanges = StateEffect.define>(); const unusedMark = Decoration.mark({ class: styles.unusedContext ?? "" }); /** Greys out the text the last Execute GraphQL left out, until the text or selection changes. */ const unusedContext = StateField.define({ create: () => Decoration.none, update(value, tr) { for (const e of tr.effects) if (e.is(setSentRanges)) return Decoration.set( outsideRanges(tr.state.doc.length, e.value) .filter((r) => r.to > r.from) .map((r) => unusedMark.range(r.from, r.to)), ); return tr.docChanged || tr.selection ? Decoration.none : value; }, provide: (f) => EditorView.decorations.from(f), }); /** Execute GraphQL in `view`: the selection, else the operation at the caret (with its fragments). */ export function runFromView(path: string, view: EditorView): Promise { const { state } = view; const { from, to } = state.selection.main; const context = queryContext(state.doc.toString(), { from, to }); if (view.state.field(unusedContext, false) !== undefined) view.dispatch({ effects: setSentRanges.of(context.ranges) }); return runQuery(path, context.text); } function Toolbar({ path, view }: { path: string; view: EditorView }) { const tx = useTx(); const projects = useGraphQL((s) => s.projects); const discovering = useGraphQL((s) => s.discovering); const running = useGraphQL((s) => s.running !== null); const endpoint = useGraphQL((s) => endpointFor(s, path)); const variablesShown = useGraphQL((s) => s.variablesShown.has(path)); useEffect(() => { if (!projects && !discovering) void discoverSchemasNow(); }, [projects, discovering]); const config = configurationFor(projects, path); const schema = projects ? schemaOf(projects, path) : null; const endpoints = schema?.endpoints ?? []; return (
void openConfiguration(path)} /> void editEnvironmentVariables(path)} /> toggleVariables(path)} />