123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- /*
- * <<
- * Davinci
- * ==
- * Copyright (C) 2016 - 2017 EDP
- * ==
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * >>
- */
- import React, { useRef, useEffect, useCallback, useMemo } from 'react'
- import AceEditor, { IAceOptions } from 'react-ace'
- import languageTools from 'ace-builds/src-min-noconflict/ext-language_tools'
- import 'ace-builds/src-min-noconflict/ext-searchbox'
- import 'ace-builds/src-min-noconflict/theme-sqlserver'
- import 'ace-builds/src-min-noconflict/mode-sql'
- import ReactAce, { IAceEditorProps } from 'react-ace/lib/ace'
- import { debounce } from 'lodash'
- import { DEFAULT_FONT_SIZE } from 'app/globalConstants'
- import Styles from '../View.less'
- type TMode =
- | 'sql'
- | 'mysql'
- | 'sqlserver'
- type TTheme =
- | 'ambiance'
- | 'chaos'
- | 'chrome'
- | 'clouds'
- | 'dawn'
- | 'eclipse'
- | 'github'
- | 'kuroir'
- | 'terminal'
- | 'textmate'
- | 'tomorrow'
- | 'twilight'
- | 'xcode'
- | 'sqlserver'
- enum EHintMeta {
- table = 'table',
- variable = 'variable',
- column = 'column'
- }
- const THEME_DEFAULT = 'sqlserver'
- const MODE_DEFAULT = 'sql'
- const EDITOR_OPTIONS: IAceOptions = {
- behavioursEnabled: true,
- enableSnippets: false,
- enableBasicAutocompletion: true,
- enableLiveAutocompletion: true,
- autoScrollEditorIntoView: true,
- wrap: true,
- useWorker: false
- }
- export interface ISqlEditorProps {
- hints: { [name: string]: string[] }
- value: string
- /**
- * 需引入对应的包 'ace-builds/src-min-noconflict/mode-${mode}'
- */
- mode?: TMode
- /**
- * 需引入对应的包 'ace-builds/src-min-noconflict/theme-${theme}'
- */
- theme?: TTheme
- editorConfig?: IAceEditorProps
- sizeChanged?: number
- onSqlChange: (sql: string) => void
- onSelect?: (sql: string) => void
- onCmdEnter?: () => void
- }
- /**
- * Editor Component
- * @param props ISqlEditorProps
- */
- function SqlEditor (props: ISqlEditorProps) {
- const refEditor = useRef<ReactAce>()
- const {
- hints,
- value,
- mode = MODE_DEFAULT,
- theme = THEME_DEFAULT,
- sizeChanged,
- editorConfig,
- onSqlChange,
- onSelect,
- onCmdEnter
- } = props
- const resize = useCallback(debounce(() => {
- refEditor.current.editor.resize()
- }, 300), [])
- const change = useCallback((sql: string) => {
- onSqlChange(sql)
- }, [])
- const selectionChange = useCallback(debounce((selection: any) => {
- const rawSelectedQueryText = refEditor.current.editor.session.doc.getTextRange(selection.getRange())
- const selectedQueryText = rawSelectedQueryText.length > 1 ? rawSelectedQueryText : null
- onSelect?.(selectedQueryText)
- }, 300), [])
- const commands = useMemo(() => [
- {
- name: 'execute',
- bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
- exec: onCmdEnter
- }
- ], [])
- useEffect(() => {
- resize()
- }, [sizeChanged])
- useEffect(() => {
- setHintsPopover(hints)
- }, [hints])
- return (
- <div className={Styles.sqlEditor}>
- <AceEditor
- ref={refEditor}
- name="aceEditor"
- width="100%"
- height="100%"
- fontSize={DEFAULT_FONT_SIZE}
- mode={mode}
- theme={theme}
- value={value}
- showPrintMargin={false}
- highlightActiveLine={true}
- setOptions={EDITOR_OPTIONS}
- commands={commands}
- onChange={change}
- onSelectionChange={selectionChange}
- {...editorConfig}
- />
- </div>
- )
- }
- interface ICompleters {
- value: string
- name?: string
- caption?: string
- meta?: string
- type?: string
- score?: number
- }
- function setHintsPopover (hints: ISqlEditorProps['hints']) {
- const {
- textCompleter,
- keyWordCompleter,
- // snippetCompleter,
- setCompleters
- } = languageTools
- const customHintsCompleter = {
- identifierRegexps: [/[a-zA-Z_0-9.\-\u00A2-\uFFFF]/],
- getCompletions: (editor, session, pos, prefix, callback) => {
- const { tableKeywords, tableColumnKeywords, variableKeywords, columns } = formatCompleterFromHints(hints)
- if (prefix[prefix.length - 1] === '.') {
- const tableName = prefix.substring(0, prefix.length - 1)
- const AliasTableColumnKeywords = genAliasTableColumnKeywords(editor, tableName, hints)
- const hintList = tableKeywords.concat(variableKeywords, AliasTableColumnKeywords, tableColumnKeywords[tableName] || [])
- return callback(null, hintList)
- }
- callback(null, tableKeywords.concat(variableKeywords, columns))
- }
- }
- const completers = [
- textCompleter,
- keyWordCompleter,
- // snippetCompleter,
- customHintsCompleter
- ]
- setCompleters(completers)
- }
- function formatCompleterFromHints (hints: ISqlEditorProps['hints']) {
- const variableKeywords: ICompleters[] = []
- const tableKeywords: ICompleters[] = []
- const tableColumnKeywords: { [tableName: string]: ICompleters[] } = {}
- const columns: ICompleters[] = []
- let score = 1000
- Object.keys(hints).forEach((key) => {
- const meta: EHintMeta = isVariable(key)
- if (!meta) {
- const { columnWithTableName, column } = genTableColumnKeywords(hints[key], key)
- tableColumnKeywords[key] = columnWithTableName
- columns.push(...column)
- tableKeywords.push({ name: key, value: key, score: score--, meta: isTable() })
- } else {
- variableKeywords.push({ score: score--, value: key, meta })
- }
- })
- return { tableKeywords, tableColumnKeywords, variableKeywords, columns }
- }
- function genTableColumnKeywords (table: string[], tableName: string) {
- let score = 100
- const columnWithTableName: ICompleters[] = []
- const column: ICompleters[] = []
- table.forEach((columnVal) => {
- const basis = { score: score--, meta: isColumn() }
- columnWithTableName.push({
- caption: `${tableName}.${columnVal}`,
- name: `${tableName}.${columnVal}`,
- value: `${tableName}.${columnVal}`,
- ...basis
- })
- column.push({ value: columnVal, name: columnVal, ...basis })
- })
- return { columnWithTableName, column }
- }
- function genAliasTableColumnKeywords (editor, aliasTableName: string, hints: ISqlEditorProps['hints']) {
- const content = editor.getSession().getValue()
- const tableName = Object.keys(hints).find((tableName) => {
- const reg = new RegExp(`.+${tableName}\\s*(as|AS)?(?=\\s+${aliasTableName}\\s*)`, 'im')
- return reg.test(content)
- })
- if (!tableName) { return [] }
- const { columnWithTableName } = genTableColumnKeywords(hints[tableName], aliasTableName)
- return columnWithTableName
- }
- function isVariable (key: string) {
- return key.startsWith('$') && key.endsWith('$') && EHintMeta.variable
- }
- function isTable (key?: string) {
- return EHintMeta.table
- }
- function isColumn (key?: string) {
- return EHintMeta.column
- }
- export default SqlEditor
|