Commit 562c27f5 authored by Enrique Alcántara's avatar Enrique Alcántara

Merge branch '198605-monaco-blob' into 'master'

Replace ACE with Monaco for Blob editing/creation

Closes #198605

See merge request gitlab-org/gitlab!34677
parents ad8945b5 a6094c42
import { __ } from '~/locale';
export const BLOB_EDITOR_ERROR = __('An error occurred while rendering the editor');
export const BLOB_PREVIEW_ERROR = __('An error occurred previewing the blob');
......@@ -3,39 +3,75 @@
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
import { BLOB_EDITOR_ERROR, BLOB_PREVIEW_ERROR } from './constants';
import TemplateSelectorMediator from '../blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
const monacoEnabled = window?.gon?.features?.monacoBlobs;
export default class EditBlob {
// The options object has:
// assetsPath, filePath, currentAction, projectId, isMarkdown
constructor(options) {
this.options = options;
this.configureAceEditor();
this.initModePanesAndLinks();
this.initSoftWrap();
this.initFileSelectors();
const { isMarkdown } = this.options;
Promise.resolve()
.then(() => {
return monacoEnabled ? this.configureMonacoEditor() : this.configureAceEditor();
})
.then(() => {
this.initModePanesAndLinks();
this.initFileSelectors();
this.initSoftWrap();
if (isMarkdown) {
addEditorMarkdownListeners(this.editor);
}
this.editor.focus();
})
.catch(() => createFlash(BLOB_EDITOR_ERROR));
}
configureMonacoEditor() {
return import(/* webpackChunkName: 'monaco_editor_lite' */ '~/editor/editor_lite').then(
EditorModule => {
const EditorLite = EditorModule.default;
const editorEl = document.getElementById('editor');
const fileNameEl =
document.getElementById('file_path') || document.getElementById('file_name');
const fileContentEl = document.getElementById('file-content');
const form = document.querySelector('.js-edit-blob-form');
this.editor = new EditorLite();
this.editor.createInstance({
el: editorEl,
blobPath: fileNameEl.value,
blobContent: editorEl.innerText,
});
fileNameEl.addEventListener('change', () => {
this.editor.updateModelLanguage(fileNameEl.value);
});
form.addEventListener('submit', () => {
fileContentEl.value = this.editor.getValue();
});
},
);
}
configureAceEditor() {
const { filePath, assetsPath, isMarkdown } = this.options;
const { filePath, assetsPath } = this.options;
ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox');
ace.config.loadModule('ace/ext/modelist');
this.editor = ace.edit('editor');
if (isMarkdown) {
addEditorMarkdownListeners(this.editor);
}
// This prevents warnings re: automatic scrolling being logged
this.editor.$blockScrolling = Infinity;
this.editor.focus();
if (filePath) {
this.editor.getSession().setMode(getModeByFileExtension(filePath));
}
......@@ -81,7 +117,7 @@ export default class EditBlob {
currentPane.empty().append(data);
currentPane.renderGFM();
})
.catch(() => createFlash(__('An error occurred previewing the blob')));
.catch(() => createFlash(BLOB_PREVIEW_ERROR));
}
this.$toggleButton.show();
......@@ -90,14 +126,19 @@ export default class EditBlob {
}
initSoftWrap() {
this.isSoftWrapped = false;
this.isSoftWrapped = Boolean(monacoEnabled);
this.$toggleButton = $('.soft-wrap-toggle');
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.$toggleButton.on('click', () => this.toggleSoftWrap());
}
toggleSoftWrap() {
this.isSoftWrapped = !this.isSoftWrapped;
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
if (monacoEnabled) {
this.editor.updateOptions({ wordWrap: this.isSoftWrapped ? 'on' : 'off' });
} else {
this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
}
}
}
import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
import { editor as monacoEditor, languages as monacoLanguages, Position, Uri } from 'monaco-editor';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import languages from '~/ide/lib/languages';
import { defaultEditorOptions } from '~/ide/lib/editor_options';
......@@ -70,6 +70,22 @@ export default class Editor {
}
getValue() {
return this.model.getValue();
return this.instance.getValue();
}
setValue(val) {
this.instance.setValue(val);
}
focus() {
this.instance.focus();
}
navigateFileStart() {
this.instance.setPosition(new Position(1, 1));
}
updateOptions(options = {}) {
this.instance.updateOptions(options);
}
}
......@@ -40,7 +40,7 @@
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2', tabindex: '-1'
.file-editor.code
%pre.js-edit-mode-pane.qa-editor#editor= params[:content] || local_assigns[:blob_data]
%pre.js-edit-mode-pane.qa-editor#editor{ data: { 'editor-loading': true } }= params[:content] || local_assigns[:blob_data]
- if local_assigns[:path]
.js-edit-mode-pane#preview.hide
.center
......
- breadcrumb_title "Repository"
- page_title "Edit", @blob.path, @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
- unless Feature.enabled?(:monaco_blobs)
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
- if @conflict
.alert.alert-danger
......
- breadcrumb_title "Repository"
- page_title "New File", @path.presence, @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
- unless Feature.enabled?(:monaco_blobs)
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
.editor-title-row
%h3.page-title.blob-new-page-title
New file
......
......@@ -2446,6 +2446,9 @@ msgstr ""
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
msgid "An error occurred while rendering the editor"
msgstr ""
msgid "An error occurred while reordering issues."
msgstr ""
......
......@@ -37,7 +37,7 @@ RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork
end
it 'allows committing to the source branch' do
find('.ace_text-input', visible: false).send_keys('Updated the readme')
execute_script("monaco.editor.getModels()[0].setValue('Updated the readme')")
click_button 'Commit changes'
wait_for_requests
......
......@@ -36,8 +36,7 @@ RSpec.describe 'Editing file blob', :js do
def fill_editor(content: 'class NextFeature\\nend\\n')
wait_for_requests
find('#editor')
execute_script("ace.edit('editor').setValue('#{content}')")
execute_script("monaco.editor.getModels()[0].setValue('#{content}')")
end
context 'from MR diff' do
......@@ -67,6 +66,15 @@ RSpec.describe 'Editing file blob', :js do
expect(find_by_id('file_path').value).to eq('ci/.gitlab-ci.yml')
end
it 'updating file path updates syntax highlighting' do
visit project_edit_blob_path(project, tree_join(branch, readme_file_path))
expect(find('#editor')['data-mode-id']).to eq('markdown')
find('#file_path').send_keys('foo.txt') do
expect(find('#editor')['data-mode-id']).to eq('plaintext')
end
end
context 'from blob file path' do
before do
stub_feature_flags(code_navigation: false)
......
......@@ -16,8 +16,7 @@ RSpec.describe 'User creates blob in new project', :js do
it 'allows the user to add a new file' do
click_link 'New file'
find('#editor')
execute_script('ace.edit("editor").setValue("Hello world")')
execute_script("monaco.editor.getModels()[0].setValue('Hello world')")
fill_in(:file_name, with: 'dummy-file')
......
......@@ -32,6 +32,8 @@ RSpec.describe 'User follows pipeline suggest nudge spec when feature is enabled
end
it 'displays suggest_gitlab_ci_yml popover' do
page.find(:css, '.gitlab-ci-yml-selector').click
popover_selector = '.suggest-gitlab-ci-yml'
expect(page).to have_css(popover_selector, visible: true)
......
......@@ -8,8 +8,9 @@ RSpec.describe 'Projects > Files > User uses soft wrap while editing file', :js
user = project.owner
sign_in user
visit project_new_blob_path(project, 'master', file_name: 'test_file-name')
page.within('.file-editor.code') do
find('.ace_text-input', visible: false).send_keys 'Touch water with paw then recoil in horror chase dog then
find('.inputarea', visible: false).send_keys 'Touch water with paw then recoil in horror chase dog then
run away chase the pig around the house eat owner\'s food, and knock
dish off table head butt cant eat out of my own dish. Cat is love, cat
is life rub face on everything poop on grasses so meow. Playing with
......@@ -26,17 +27,20 @@ RSpec.describe 'Projects > Files > User uses soft wrap while editing file', :js
it 'user clicks the "Soft wrap" button and then "No wrap" button' do
wrapped_content_width = get_content_width
toggle_button.click
expect(toggle_button).to have_content 'No wrap'
unwrapped_content_width = get_content_width
expect(unwrapped_content_width).to be < wrapped_content_width
toggle_button.click
expect(toggle_button).to have_content 'Soft wrap'
expect(get_content_width).to be > unwrapped_content_width
toggle_button.click do
expect(toggle_button).to have_content 'Soft wrap'
unwrapped_content_width = get_content_width
expect(unwrapped_content_width).to be > wrapped_content_width
end
toggle_button.click do
expect(toggle_button).to have_content 'No wrap'
expect(get_content_width).to be < unwrapped_content_width
end
end
def get_content_width
find('.ace_content')[:style].slice!(/width: \d+/).slice!(/\d+/).to_i
find('.view-lines', visible: false)[:style].slice!(/width: \d+/).slice!(/\d+/).to_i
end
end
......@@ -25,6 +25,6 @@ RSpec.describe 'Projects > Files > User wants to add a .gitignore file' do
expect(page).to have_css('.gitignore-selector .dropdown-toggle-text', text: 'Apply a template')
expect(page).to have_content('/.bundle')
expect(page).to have_content('# Gemfile.lock, .ruby-version, .ruby-gemset')
expect(page).to have_content('config/initializers/secret_token.rb')
end
end
......@@ -67,7 +67,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
file_name = find('#file_name')
file_name.set options[:file_name] || 'README.md'
find('.ace_text-input', visible: false).send_keys.native.send_keys options[:file_content] || 'Some content'
find('.monaco-editor textarea').send_keys.native.send_keys options[:file_content] || 'Some content'
click_button 'Commit changes'
end
......@@ -89,7 +89,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
it 'creates and commit a new file' do
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
......@@ -105,7 +105,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
it 'creates and commit a new file with new lines at the end of file' do
find('#editor')
execute_script('ace.edit("editor").setValue("Sample\n\n\n")')
execute_script('monaco.editor.getModels()[0].setValue("Sample\n\n\n")')
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
......@@ -117,7 +117,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
find('.js-edit-blob').click
find('#editor')
expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n")
expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq("Sample\n\n\n")
end
it 'creates and commit a new file with a directory name' do
......@@ -126,7 +126,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
expect(page).to have_selector('.file-editor')
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
......@@ -141,7 +141,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
expect(page).to have_selector('.file-editor')
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
fill_in(:branch_name, with: 'new_branch_name', visible: true)
......@@ -176,7 +176,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
expect(page).to have_selector('.file-editor')
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:file_name, with: 'not_a_file.md')
fill_in(:commit_message, with: 'New commit message', visible: true)
......
......@@ -46,9 +46,9 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq('*.rbca')
end
it 'does not show the edit link if a file is binary' do
......@@ -67,7 +67,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
......@@ -85,7 +85,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
fill_in(:branch_name, with: 'new_branch_name', visible: true)
click_button('Commit changes')
......@@ -103,7 +103,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
click_link('Preview changes')
expect(page).to have_css('.line_holder.new')
......@@ -148,9 +148,9 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq('*.rbca')
end
it 'opens the Web IDE in a forked project', :sidekiq_might_not_need_inline do
......@@ -178,7 +178,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
find('.file-editor', match: :first)
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
......@@ -207,7 +207,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
expect(page).not_to have_button('Cancel')
find('#editor')
execute_script("ace.edit('editor').setValue('*.rbca')")
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')")
fill_in(:commit_message, with: 'Another commit', visible: true)
click_button('Commit changes')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment