CoolFace
Datasetpublic

gradio/frontend

sourceHugging Faceupdated 18h agoView on Hugging Face
1likes442kdownloads
Code.svelte315 linesDownload Raw Back to shared
1<script lang="ts">2	import { createEventDispatcher, onMount } from "svelte";3	import {4		EditorView,5		ViewUpdate,6		keymap,7		placeholder as placeholderExt,8		lineNumbers9	} from "@codemirror/view";10	import { StateEffect, EditorState, type Extension } from "@codemirror/state";11	import { indentWithTab } from "@codemirror/commands";12	import { autocompletion, acceptCompletion } from "@codemirror/autocomplete";13 14	import { basicDark } from "cm6-theme-basic-dark";15	import { basicLight } from "cm6-theme-basic-light";16	import { basicSetup } from "./extensions";17	import { getLanguageExtension } from "./language";18 19	export let class_names = "";20	export let value = "";21	export let dark_mode: boolean;22	export let basic = true;23	export let language: string;24	export let lines = 5;25	export let max_lines: number | null = null;26	export let extensions: Extension[] = [];27	export let use_tab = true;28	export let readonly = false;29	export let placeholder: string | HTMLElement | null | undefined = undefined;30	export let wrap_lines = false;31	export let show_line_numbers = true;32	export let autocomplete = false;33 34	const dispatch = createEventDispatcher<{35		change: string;36		blur: undefined;37		focus: undefined;38		input: undefined;39	}>();40	let lang_extension: Extension | undefined;41	let element: HTMLDivElement;42	let view: EditorView;43 44	$: get_lang(language);45 46	async function get_lang(val: string): Promise<void> {47		const ext = await getLanguageExtension(val);48		lang_extension = ext;49	}50 51	$: (reconfigure(), lang_extension, readonly);52	$: set_doc(value);53	$: update_lines();54 55	function set_doc(new_doc: string): void {56		if (view && new_doc !== view.state.doc.toString()) {57			view.dispatch({58				changes: {59					from: 0,60					to: view.state.doc.length,61					insert: new_doc62				}63			});64		}65	}66 67	function update_lines(): void {68		if (view) {69			view.requestMeasure({ read: resize });70		}71	}72 73	function create_editor_view(): EditorView {74		const editorView = new EditorView({75			parent: element,76			state: create_editor_state(value)77		});78		editorView.dom.addEventListener("focus", handle_focus, true);79		editorView.dom.addEventListener("blur", handle_blur, true);80		return editorView;81	}82 83	function handle_focus(): void {84		dispatch("focus");85	}86 87	function handle_blur(): void {88		dispatch("blur");89	}90 91	function getGutterLineHeight(_view: EditorView): string | null {92		let elements = _view.dom.querySelectorAll<HTMLElement>(".cm-gutterElement");93		if (elements.length === 0) {94			return null;95		}96		for (var i = 0; i < elements.length; i++) {97			let node = elements[i];98			let height = getComputedStyle(node)?.height ?? "0px";99			if (height != "0px") {100				return height;101			}102		}103		return null;104	}105 106	function resize(_view: EditorView): any {107		let scroller = _view.dom.querySelector<HTMLElement>(".cm-scroller");108		if (!scroller) {109			return null;110		}111		const lineHeight = getGutterLineHeight(_view);112		if (!lineHeight) {113			return null;114		}115 116		const minLines = lines == 1 ? 1 : lines + 1;117		scroller.style.minHeight = `calc(${lineHeight} * ${minLines})`;118		if (max_lines)119			scroller.style.maxHeight = `calc(${lineHeight} * ${max_lines + 1})`;120	}121 122	import { Transaction } from "@codemirror/state";123 124	function is_user_input(update: ViewUpdate): boolean {125		return update.transactions.some(126			(tr) => tr.annotation(Transaction.userEvent) != null127		);128	}129 130	function handle_change(vu: ViewUpdate): void {131		if (!vu.docChanged) return;132 133		const doc = vu.state.doc;134		const text = doc.toString();135		value = text;136 137		const user_change = is_user_input(vu);138		if (user_change) {139			dispatch("change", text);140			dispatch("input");141		} else {142			dispatch("change", text);143		}144 145		view.requestMeasure({ read: resize });146	}147 148	function get_extensions(): Extension[] {149		const stateExtensions = [150			...get_base_extensions(151				basic,152				use_tab,153				placeholder,154				readonly,155				lang_extension,156				show_line_numbers157			),158			FontTheme,159			...get_theme(),160			...extensions161		];162		return stateExtensions;163	}164 165	const FontTheme = EditorView.theme({166		"&": {167			fontSize: "var(--text-sm)",168			backgroundColor: "var(--border-color-secondary)"169		},170		".cm-content": {171			paddingTop: "5px",172			paddingBottom: "5px",173			color: "var(--body-text-color)",174			fontFamily: "var(--font-mono)",175			minHeight: "100%"176		},177		".cm-gutterElement": {178			marginRight: "var(--spacing-xs)"179		},180		".cm-gutters": {181			marginRight: "1px",182			borderRight: "1px solid var(--border-color-primary)",183			backgroundColor: "var(--block-background-fill);",184			color: "var(--body-text-color-subdued)"185		},186		".cm-focused": {187			outline: "none"188		},189		".cm-scroller": {190			height: "auto"191		},192		".cm-cursor": {193			borderLeftColor: "var(--body-text-color)"194		}195	});196 197	const AutocompleteTheme = EditorView.theme({198		".cm-tooltip-autocomplete": {199			"& > ul": {200				backgroundColor: "var(--background-fill-primary)",201				color: "var(--body-text-color)"202			},203			"& > ul > li[aria-selected]": {204				backgroundColor: "var(--color-accent-soft)",205				color: "var(--body-text-color)"206			}207		}208	});209 210	function create_editor_state(_value: string | null | undefined): EditorState {211		return EditorState.create({212			doc: _value ?? undefined,213			extensions: get_extensions()214		});215	}216 217	function get_base_extensions(218		basic: boolean,219		use_tab: boolean,220		placeholder: string | HTMLElement | null | undefined,221		readonly: boolean,222		lang: Extension | null | undefined,223		show_line_numbers: boolean224	): Extension[] {225		const extensions: Extension[] = [226			EditorView.editable.of(!readonly),227			EditorState.readOnly.of(readonly),228			EditorView.contentAttributes.of({ "aria-label": "Code input container" })229		];230 231		if (basic) {232			extensions.push(basicSetup);233		}234		if (use_tab) {235			extensions.push(236				keymap.of([{ key: "Tab", run: acceptCompletion }, indentWithTab])237			);238		}239		if (placeholder) {240			extensions.push(placeholderExt(placeholder));241		}242		if (lang) {243			extensions.push(lang);244		}245		if (show_line_numbers) {246			extensions.push(lineNumbers());247		}248		if (autocomplete) {249			extensions.push(autocompletion());250			extensions.push(AutocompleteTheme);251		}252 253		extensions.push(EditorView.updateListener.of(handle_change));254		if (wrap_lines) {255			extensions.push(EditorView.lineWrapping);256		}257 258		return extensions;259	}260 261	function get_theme(): Extension[] {262		const extensions: Extension[] = [];263 264		if (dark_mode) {265			extensions.push(basicDark);266		} else {267			extensions.push(basicLight);268		}269		return extensions;270	}271 272	function reconfigure(): void {273		view?.dispatch({274			effects: StateEffect.reconfigure.of(get_extensions())275		});276	}277 278	onMount(() => {279		view = create_editor_view();280		return () => view?.destroy();281	});282</script>283 284<div class="wrap">285	<div class="codemirror-wrapper {class_names}" bind:this={element} />286</div>287 288<style>289	.wrap {290		display: flex;291		flex-direction: column;292		flex-grow: 1;293		margin: 0;294		padding: 0;295		height: 100%;296	}297	.codemirror-wrapper {298		flex-grow: 1;299		overflow: auto;300	}301 302	:global(.cm-editor) {303		height: 100%;304	}305 306	/* Dunno why this doesn't work through the theme API -- don't remove*/307	:global(.cm-selectionBackground) {308		background-color: #b9d2ff30 !important;309	}310 311	:global(.cm-focused) {312		outline: none !important;313	}314</style>315 
gradio/frontend · CoolFace