| // Copyright (c) 2021, Compiler Explorer Authors |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // * Redistributions of source code must retain the above copyright notice, |
| // this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above copyright |
| // notice, this list of conditions and the following disclaimer in the |
| // documentation and/or other materials provided with the distribution. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| // POSSIBILITY OF SUCH DAMAGE. |
| |
| import * as monaco from 'monaco-editor'; |
| |
| import { Alert } from './alert'; |
| import { getStoredSettings } from './settings'; |
| import { FormatRequestOptions, FormatResponse } from './formatter-registry.interfaces'; |
| import { SiteSettings } from './settings.interfaces'; |
| |
| // Proxy function to emit the error to the alert system |
| const onFormatError = (cause: string, source: string) => { |
| const alertSystem = new Alert(); |
| alertSystem.notify(`We encountered an error formatting your code: ${cause}`, { |
| group: 'formatting', |
| alertClass: 'notification-error', |
| }); |
| return source; |
| }; |
| |
| const getFormattedCode = async ({ source, formatterId, base, tabWidth, useSpaces }: FormatRequestOptions) => { |
| const res = await fetch(`${window.location.origin}${window.httpRoot}api/format/${formatterId}`, { |
| method: 'POST', |
| credentials: 'include', |
| headers: { |
| 'Content-Type': 'application/json', |
| 'Accept': 'application/json', |
| }, |
| body: JSON.stringify({ source, base, tabWidth, useSpaces }), |
| }); |
| const body = await res.json() as FormatResponse; |
| if (res.status === 200 && body.exit === 0) { |
| // API sent 200 and we have a valid response |
| return body.answer; |
| } |
| // We had an error (either HTTP request error, or API error) |
| // Figure out which it is, show it to the user, and reject the promise |
| const cause = body?.answer ?? res.statusText |
| throw new Error(cause); |
| }; |
| |
| /** |
| * Create a monaco DocumentFormattingEditProvider for a registered monaco |
| * language. |
| * |
| * @param language The monaco-editor registered language to format code for |
| * @param formatter The CE format API backend to use |
| * @param isOneTrueStyle Whether the CE format API backend has one true style |
| */ |
| const getDocumentFormatter = ( |
| language: string, |
| formatter: string, |
| isOneTrueStyle: boolean, |
| ): monaco.languages.DocumentFormattingEditProvider => ({ |
| async provideDocumentFormattingEdits( |
| model: monaco.editor.ITextModel, |
| options: monaco.languages.FormattingOptions, |
| token: monaco.CancellationToken, |
| ): Promise<monaco.languages.TextEdit[]> { |
| const settings: SiteSettings = getStoredSettings(); |
| // If there is only one style, return __DefaultStyle. |
| const base = isOneTrueStyle ? '__DefaultStyle' : settings.formatBase; |
| const source = model.getValue(); |
| // Request the formatted code. If that API call fails, we just back off |
| // and return the user's old code. |
| const formattedSource = await getFormattedCode({ |
| formatterId: formatter, |
| tabWidth: settings.tabWidth, |
| useSpaces: settings.useSpaces, |
| source, |
| base, |
| }).catch(err => onFormatError(err, source)); |
| return [{ |
| range: model.getFullModelRange(), |
| text: formattedSource, |
| }]; |
| }, |
| }); |
| |
| /** Register a monaco-editor language's default document formatting provider */ |
| const register = (lang: string, formatter: string, isOneTrueStyle: boolean) => { |
| const provider = getDocumentFormatter(lang, formatter, isOneTrueStyle); |
| monaco.languages.registerDocumentFormattingEditProvider(lang, provider); |
| } |
| |
| register('cppp', 'clangformat', false); |
| register('nc', 'clangformat', false); |
| register('go', 'gofmt', true); |
| register('rust', 'rustfmt', true); |