diff options
Diffstat (limited to 'app/assets/javascripts/editor/extensions')
4 files changed, 154 insertions, 0 deletions
diff --git a/app/assets/javascripts/editor/extensions/editor_ci_schema_ext.js b/app/assets/javascripts/editor/extensions/editor_ci_schema_ext.js new file mode 100644 index 00000000000..eb47c20912e --- /dev/null +++ b/app/assets/javascripts/editor/extensions/editor_ci_schema_ext.js @@ -0,0 +1,38 @@ +import Api from '~/api'; +import { registerSchema } from '~/ide/utils'; +import { EditorLiteExtension } from './editor_lite_extension_base'; +import { EXTENSION_CI_SCHEMA_FILE_NAME_MATCH } from '../constants'; + +export class CiSchemaExtension extends EditorLiteExtension { + /** + * Registers a syntax schema to the editor based on project + * identifier and commit. + * + * The schema is added to the file that is currently edited + * in the editor. + * + * @param {Object} opts + * @param {String} opts.projectNamespace + * @param {String} opts.projectPath + * @param {String?} opts.ref - Current ref. Defaults to master + */ + registerCiSchema({ projectNamespace, projectPath, ref = 'master' } = {}) { + const ciSchemaPath = Api.buildUrl(Api.projectFileSchemaPath) + .replace(':namespace_path', projectNamespace) + .replace(':project_path', projectPath) + .replace(':ref', ref) + .replace(':filename', EXTENSION_CI_SCHEMA_FILE_NAME_MATCH); + // In order for workers loaded from `data://` as the + // ones loaded by monaco editor, we use absolute URLs + // to fetch schema files, hence the `gon.gitlab_url` + // reference. This prevents error: + // "Failed to execute 'fetch' on 'WorkerGlobalScope'" + const absoluteSchemaUrl = gon.gitlab_url + ciSchemaPath; + const modelFileName = this.getModel().uri.path.split('/').pop(); + + registerSchema({ + uri: absoluteSchemaUrl, + fileMatch: [modelFileName], + }); + } +} diff --git a/app/assets/javascripts/editor/extensions/editor_file_template_ext.js b/app/assets/javascripts/editor/extensions/editor_file_template_ext.js new file mode 100644 index 00000000000..f5474318447 --- /dev/null +++ b/app/assets/javascripts/editor/extensions/editor_file_template_ext.js @@ -0,0 +1,8 @@ +import { Position } from 'monaco-editor'; +import { EditorLiteExtension } from './editor_lite_extension_base'; + +export class FileTemplateExtension extends EditorLiteExtension { + navigateFileStart() { + this.setPosition(new Position(1, 1)); + } +} diff --git a/app/assets/javascripts/editor/extensions/editor_lite_extension_base.js b/app/assets/javascripts/editor/extensions/editor_lite_extension_base.js new file mode 100644 index 00000000000..8d350068973 --- /dev/null +++ b/app/assets/javascripts/editor/extensions/editor_lite_extension_base.js @@ -0,0 +1,11 @@ +import { ERROR_INSTANCE_REQUIRED_FOR_EXTENSION } from '../constants'; + +export class EditorLiteExtension { + constructor({ instance, ...options } = {}) { + if (instance) { + Object.assign(instance, options); + } else if (Object.entries(options).length) { + throw new Error(ERROR_INSTANCE_REQUIRED_FOR_EXTENSION); + } + } +} diff --git a/app/assets/javascripts/editor/extensions/editor_markdown_ext.js b/app/assets/javascripts/editor/extensions/editor_markdown_ext.js new file mode 100644 index 00000000000..2ce003753f7 --- /dev/null +++ b/app/assets/javascripts/editor/extensions/editor_markdown_ext.js @@ -0,0 +1,97 @@ +import { EditorLiteExtension } from './editor_lite_extension_base'; + +export class EditorMarkdownExtension extends EditorLiteExtension { + getSelectedText(selection = this.getSelection()) { + const { startLineNumber, endLineNumber, startColumn, endColumn } = selection; + const valArray = this.getValue().split('\n'); + let text = ''; + if (startLineNumber === endLineNumber) { + text = valArray[startLineNumber - 1].slice(startColumn - 1, endColumn - 1); + } else { + const startLineText = valArray[startLineNumber - 1].slice(startColumn - 1); + const endLineText = valArray[endLineNumber - 1].slice(0, endColumn - 1); + + for (let i = startLineNumber, k = endLineNumber - 1; i < k; i += 1) { + text += `${valArray[i]}`; + if (i !== k - 1) text += `\n`; + } + text = text + ? [startLineText, text, endLineText].join('\n') + : [startLineText, endLineText].join('\n'); + } + return text; + } + + replaceSelectedText(text, select = undefined) { + const forceMoveMarkers = !select; + this.executeEdits('', [{ range: this.getSelection(), text, forceMoveMarkers }]); + } + + moveCursor(dx = 0, dy = 0) { + const pos = this.getPosition(); + pos.column += dx; + pos.lineNumber += dy; + this.setPosition(pos); + } + + /** + * Adjust existing selection to select text within the original selection. + * - If `selectedText` is not supplied, we fetch selected text with + * + * ALGORITHM: + * + * MULTI-LINE SELECTION + * 1. Find line that contains `toSelect` text. + * 2. Using the index of this line and the position of `toSelect` text in it, + * construct: + * * newStartLineNumber + * * newStartColumn + * + * SINGLE-LINE SELECTION + * 1. Use `startLineNumber` from the current selection as `newStartLineNumber` + * 2. Find the position of `toSelect` text in it to get `newStartColumn` + * + * 3. `newEndLineNumber` — Since this method is supposed to be used with + * markdown decorators that are pretty short, the `newEndLineNumber` is + * suggested to be assumed the same as the startLine. + * 4. `newEndColumn` — pretty obvious + * 5. Adjust the start and end positions of the current selection + * 6. Re-set selection on the instance + * + * @param {string} toSelect - New text to select within current selection. + * @param {string} selectedText - Currently selected text. It's just a + * shortcut: If it's not supplied, we fetch selected text from the instance + */ + selectWithinSelection(toSelect, selectedText) { + const currentSelection = this.getSelection(); + if (currentSelection.isEmpty() || !toSelect) { + return; + } + const text = selectedText || this.getSelectedText(currentSelection); + let lineShift; + let newStartLineNumber; + let newStartColumn; + + const textLines = text.split('\n'); + + if (textLines.length > 1) { + // Multi-line selection + lineShift = textLines.findIndex((line) => line.indexOf(toSelect) !== -1); + newStartLineNumber = currentSelection.startLineNumber + lineShift; + newStartColumn = textLines[lineShift].indexOf(toSelect) + 1; + } else { + // Single-line selection + newStartLineNumber = currentSelection.startLineNumber; + newStartColumn = currentSelection.startColumn + text.indexOf(toSelect); + } + + const newEndLineNumber = newStartLineNumber; + const newEndColumn = newStartColumn + toSelect.length; + + const newSelection = currentSelection + .setStartPosition(newStartLineNumber, newStartColumn) + .setEndPosition(newEndLineNumber, newEndColumn); + + this.setSelection(newSelection); + } +} |