diff --git a/windows/src/developer/TIKE/http/Keyman.Developer.System.HttpServer.App.pas b/windows/src/developer/TIKE/http/Keyman.Developer.System.HttpServer.App.pas index cb370b2972..57ff80339c 100644 --- a/windows/src/developer/TIKE/http/Keyman.Developer.System.HttpServer.App.pas +++ b/windows/src/developer/TIKE/http/Keyman.Developer.System.HttpServer.App.pas @@ -20,6 +20,8 @@ type ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); procedure RespondHelp(doc: string; AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); + procedure RespondSettings(doc: string; AContext: TIdContext; + ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); public constructor Create; procedure ProcessRequest(AContext: TIdContext; @@ -30,11 +32,14 @@ implementation uses System.Classes, + System.JSON, System.SysUtils, System.Variants, Xml.XMLDoc, Xml.XMLIntf, + JsonUtil, + KeymanDeveloperOptions, Keyman.Developer.System.Project.ProjectFile, Keyman.Developer.System.Project.ProjectSaver, RedistFiles; @@ -220,6 +225,42 @@ begin RespondState; end; +procedure TAppHttpResponder.RespondSettings(doc: string; AContext: TIdContext; + ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); + + procedure RespondEditorSettings; + var + root: TJSONObject; + begin + // Respond as JSON + + root := TJSONObject.Create; + try + // Basic settings - tabs + + // TODO: This is technically not thread safe. + root.AddPair('useTabChar', TJSONBool.Create(FKeymanDeveloperOptions.UseTabChar)); + root.AddPair('indentSize', TJSONNumber.Create(FKeymanDeveloperOptions.IndentSize)); + + // Theme data + + AResponseInfo.ContentType := 'application/json'; + AResponseInfo.CharSet := 'UTF-8'; + AResponseInfo.ContentText := JSONToString(root); + finally + root.Free; + end; + end; + +begin + if doc = 'settings/editor' then + begin + RespondEditorSettings; + end + else + Respond404(AContext, ARequestInfo, AResponseInfo); +end; + constructor TAppHttpResponder.Create; begin FAppRoot := ExtractFilePath(ParamStr(0)) + 'xml\app\'; @@ -254,6 +295,11 @@ begin RespondHelp(doc, AContext, ARequestInfo, AResponseInfo); Exit; end + else if Copy(doc, 1, 9) = 'settings/' then + begin + RespondSettings(doc, AContext, ARequestInfo, AResponseInfo); + Exit; + end else begin if IncludesParentFolderReference(doc) then diff --git a/windows/src/developer/TIKE/http/Keyman.Developer.System.HttpServer.AppSource.pas b/windows/src/developer/TIKE/http/Keyman.Developer.System.HttpServer.AppSource.pas index 910f0fb4b0..e3a0b988e7 100644 --- a/windows/src/developer/TIKE/http/Keyman.Developer.System.HttpServer.AppSource.pas +++ b/windows/src/developer/TIKE/http/Keyman.Developer.System.HttpServer.AppSource.pas @@ -91,6 +91,7 @@ begin if ARequestInfo.Document = '/app/source/file' then begin + // TODO: We should be passing a token to the browser for future POST security Filename := ARequestInfo.Params.Values['Filename']; if ARequestInfo.CommandType = hcGET then diff --git a/windows/src/developer/TIKE/xml/app/editor/editor.js b/windows/src/developer/TIKE/xml/app/editor/editor.js index f378a57a8f..7b8a705763 100644 --- a/windows/src/developer/TIKE/xml/app/editor/editor.js +++ b/windows/src/developer/TIKE/xml/app/editor/editor.js @@ -10,6 +10,10 @@ window.editorGlobalContext = { loading: false }; +async function loadSettings() { + return await $.getJSON('/app/settings/editor'); +} + (function(context) { var editor = null; var errorRange = null; @@ -19,91 +23,114 @@ window.editorGlobalContext = { let params = (new URL(location)).searchParams; let filename = params.get('filename'); let mode = params.get('mode'); - + if(!mode) { mode = 'keyman'; } - + const settings = loadSettings(); + /** Initialize the editor */ - require.config({ paths: { 'vs': '../lib/monaco/min/vs' } }); - require(['vs/editor/editor.main','language.keyman'], function (_editor, _language) { - - // - // Register Keyman .kmn tokens provider and language formatter - // https://github.com/Microsoft/monaco-editor/blob/master/test/playground.generated/extending-language-services-custom-languages.html - // - - monaco.languages.register({ id: 'keyman' }); - monaco.languages.setMonarchTokensProvider('keyman', _language.language); - - // - // Create editor and load source file - // + require.config({ paths: { + 'vs': '../lib/monaco/min/vs' + } }); - editor = monaco.editor.create(document.getElementById('editor'), { - language: mode, - minimap: { - enabled: false - }, - glyphMargin: true, - lineNumbersMinChars: 2, - disableMonospaceOptimizations: true - }); + settings.then(function(_settings){ + require(['vs/editor/editor.main','language.keyman'], function (_editor, _language) { - $.get("/app/source/file", - { - Filename: filename - }, - function (response) { - context.loading = true; - editor.setValue(response); - context.loading = false; - }, - "text" - ); - - // - // Set initial fonts - // - - context.setFonts({ - codeFont: { name: params.get('codeFontName'), size: params.get('codeFontSize') }, - charFont: { name: params.get('charFontName'), size: params.get('charFontSize') } - }); - - // - // Setup callbacks - // + _settings = $.extend({ + useTabChar: false, + indentSize: 4 + }, _settings); - const model = editor.getModel(); - model.onDidChangeContent(() => { - // Even when loading, we post back the data to the backend so we have an original version - $.post("/app/source/file", { - Filename: filename, - Data: model.getValue() - // delta.start, delta.end, delta.lines, delta.action + // + // Define a custom theme + // + + if(_settings.theme) { + //monaco.editor.setTheme(); + } + + // + // Register Keyman .kmn tokens provider and language formatter + // https://github.com/Microsoft/monaco-editor/blob/master/test/playground.generated/extending-language-services-custom-languages.html + // + + monaco.languages.register({ id: 'keyman' }); + monaco.languages.setMonarchTokensProvider('keyman', _language.language); + + // + // Create editor and load source file + // + + editor = monaco.editor.create(document.getElementById('editor'), { + language: mode, + minimap: { + enabled: false + }, + glyphMargin: true, + useTabStops: _settings.useTabChar, + lineNumbersMinChars: 2, + disableMonospaceOptimizations: true }); - if (!context.loading) { - context.highlightError(); // clear the selected error - command('modified'); - updateState(); - } - }); - editor.onDidChangeCursorPosition(updateState); - editor.onDidChangeCursorSelection(updateState); - editor.onMouseDown(e => { - if (e.target.type !== monaco.editor.MouseTargetType.GUTTER_GLYPH_MARGIN) { - return; - } - command('breakpoint-clicked,'+(e.target.position.lineNumber-1)); + $.get("/app/source/file", + { + Filename: filename + }, + function (response) { + context.loading = true; + editor.setValue(response); + context.loading = false; + }, + "text" + ); + + // + // Set initial fonts + // + + context.setFonts({ + codeFont: { name: params.get('codeFontName'), size: params.get('codeFontSize') }, + charFont: { name: params.get('charFontName'), size: params.get('charFontSize') } + }); + + // + // Setup callbacks + // + + const model = editor.getModel(); + model.updateOptions({ + tabSize: _settings.indentSize + }); + + model.onDidChangeContent(() => { + // Even when loading, we post back the data to the backend so we have an original version + $.post("/app/source/file", { + Filename: filename, + Data: model.getValue() + // delta.start, delta.end, delta.lines, delta.action + }); + if (!context.loading) { + context.highlightError(); // clear the selected error + command('modified'); + updateState(); + } + }); + + editor.onDidChangeCursorPosition(updateState); + editor.onDidChangeCursorSelection(updateState); + editor.onMouseDown(e => { + if (e.target.type !== monaco.editor.MouseTargetType.GUTTER_GLYPH_MARGIN) { + return; + } + command('breakpoint-clicked,'+(e.target.position.lineNumber-1)); + }); }); }); - // // Search and replace interfaces //