From 959c67258ac57c30ff5b9d1761df3f32e8abeb06 Mon Sep 17 00:00:00 2001 From: Amarsh Anand Date: Tue, 29 Aug 2023 15:16:55 -0700 Subject: [PATCH 1/4] code window is always visible --- src/linecomps.tsx | 28 ++++++++++++++++++++++++++++ src/prompt.less | 16 +++++++++++++--- src/simplerenderer.tsx | 2 ++ src/view/code.tsx | 26 ++++++++++++++++++++------ 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/linecomps.tsx b/src/linecomps.tsx index dae0e060be..a41fe1614c 100644 --- a/src/linecomps.tsx +++ b/src/linecomps.tsx @@ -599,6 +599,33 @@ class LineCmd extends React.Component< }; } + scrollToAlign = () => { + const container = document.getElementsByClassName("lines")[0]; + const targetDiv = this.lineRef.current; + const targetPosition = targetDiv.getBoundingClientRect(); + const containerPosition = container.getBoundingClientRect(); + + // Check if the top of the targetDiv is above the container's visible area + if (targetPosition.top < containerPosition.top) { + // Scroll up to make the top of the targetDiv visible + const scrollAmount = container.scrollTop + targetPosition.top - containerPosition.top; + container.scrollTo({ + top: scrollAmount, + behavior: "smooth", + }); + } + // Check if the bottom of the targetDiv is below the container's visible area + else if (targetPosition.bottom > containerPosition.bottom) { + // Scroll down to make the bottom of the targetDiv visible + const scrollAmount = container.scrollTop + targetPosition.bottom - containerPosition.bottom; + container.scrollTo({ + top: scrollAmount, + behavior: "smooth", + }); + } + // If both conditions are false, then targetDiv is already fully visible, no scrolling needed + }; + render() { let { screen, line, width, staticRender, visible, topBorder, renderMode } = this.props; let model = GlobalModel; @@ -733,6 +760,7 @@ class LineCmd extends React.Component< plugin={rendererPlugin} onHeightChange={this.handleHeightChange} initParams={this.makeRendererModelInitializeParams()} + scrollToAlign={this.scrollToAlign} /> diff --git a/src/prompt.less b/src/prompt.less index 001a66a31e..92643e264f 100644 --- a/src/prompt.less +++ b/src/prompt.less @@ -239,12 +239,11 @@ input[type="checkbox"] { } .dropdown { - background: rgb(180, 180, 180); + background: #dbdbdb; color: black; border-radius: 4px 4px 0 0; font-size: 10px; - font-family: system-ui; - padding: 1px 0 3px 3px; + padding: 2px 0 5px 5px; outline: none; } } @@ -254,6 +253,17 @@ input[type="checkbox"] { .monaco-editor .monaco-editor-background { background-color: rgba(255, 255, 255, 0.075) !important; } + .cmd-hints { + display: inline-block; + position: relative; + margin-right: 26px; + } + .hint-item { + border-radius: 4px 4px 0 0; + padding: 3px 9px 2px 8px; + line-height: 15px; + text-align: center; + } } .renderer-container.json-renderer { diff --git a/src/simplerenderer.tsx b/src/simplerenderer.tsx index 4e48ec570b..b1ff3b81d1 100644 --- a/src/simplerenderer.tsx +++ b/src/simplerenderer.tsx @@ -120,6 +120,7 @@ class SimpleBlobRenderer extends React.Component< plugin: RendererPluginType; onHeightChange: () => void; initParams: RendererModelInitializeParams; + scrollToAlign: () => void; }, {} > { @@ -209,6 +210,7 @@ class SimpleBlobRenderer extends React.Component< context={simpleModel.context} opts={simpleModel.opts} savedHeight={simpleModel.savedHeight} + scrollToAlign={this.props.scrollToAlign} /> ); diff --git a/src/view/code.tsx b/src/view/code.tsx index 5a76c01cff..649cdf7f8d 100644 --- a/src/view/code.tsx +++ b/src/view/code.tsx @@ -16,6 +16,7 @@ class SourceCodeRenderer extends React.Component< context: RendererContext; opts: RendererOpts; savedHeight: number; + scrollToAlign: () => void; }, {} > { @@ -23,6 +24,7 @@ class SourceCodeRenderer extends React.Component< language: OV = mobx.observable.box(""); languages: OV = mobx.observable.box([]); selectedLanguage: OV = mobx.observable.box(""); + isFullWindow: OV = mobx.observable.box(false); // load this from opts editorRef; constructor(props) { @@ -66,6 +68,11 @@ class SourceCodeRenderer extends React.Component< } }; + toggleFit = () => { + this.isFullWindow.set(!this.isFullWindow.get()); + setTimeout(() => this.props.scrollToAlign(), 10); + }; + render() { let opts = this.props.opts; let lang = this.language.get(); @@ -73,11 +80,13 @@ class SourceCodeRenderer extends React.Component< if (!code) { return
; } - const noOfLines = code.split("\n").length; - const editorHeight = Math.min( - noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10, - parseInt(opts.maxSize.height) - ); + const fullWindowHeight = parseInt(opts.maxSize.height); + let editorHeight = fullWindowHeight; + if (!this.isFullWindow.get()) { + const noOfLines = code.split("\n").length; + editorHeight = Math.min(noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10, fullWindowHeight); + } + return (
@@ -100,7 +109,7 @@ class SourceCodeRenderer extends React.Component< className="dropdown" value={this.selectedLanguage.get()} onChange={this.handleLanguageChange} - style={{ maxWidth: "5rem", marginRight: "24px" }} + style={{ minWidth: "6rem", maxWidth: "6rem", marginRight: "8px" }} > {this.languages.get().map((lang, index) => ( ))} +
+
+ {this.isFullWindow.get() ? `shrink` : `expand`} +
+
); From d8e3ea1333b82776f9cac2867dd550bc953e1aa9 Mon Sep 17 00:00:00 2001 From: Amarsh Anand Date: Tue, 29 Aug 2023 17:41:17 -0700 Subject: [PATCH 2/4] gotten resizing done. need more work --- src/linecomps.tsx | 5 +++-- src/simplerenderer.tsx | 4 ++-- src/view/code.tsx | 39 ++++++++++++++++++++++++++++----------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/linecomps.tsx b/src/linecomps.tsx index a41fe1614c..ac2d1b2a30 100644 --- a/src/linecomps.tsx +++ b/src/linecomps.tsx @@ -599,7 +599,8 @@ class LineCmd extends React.Component< }; } - scrollToAlign = () => { + scrollToBringIntoViewport = () => { + console.log(`here`); const container = document.getElementsByClassName("lines")[0]; const targetDiv = this.lineRef.current; const targetPosition = targetDiv.getBoundingClientRect(); @@ -760,7 +761,7 @@ class LineCmd extends React.Component< plugin={rendererPlugin} onHeightChange={this.handleHeightChange} initParams={this.makeRendererModelInitializeParams()} - scrollToAlign={this.scrollToAlign} + scrollToBringIntoViewport={this.scrollToBringIntoViewport} /> diff --git a/src/simplerenderer.tsx b/src/simplerenderer.tsx index b1ff3b81d1..689f4ba05e 100644 --- a/src/simplerenderer.tsx +++ b/src/simplerenderer.tsx @@ -120,7 +120,7 @@ class SimpleBlobRenderer extends React.Component< plugin: RendererPluginType; onHeightChange: () => void; initParams: RendererModelInitializeParams; - scrollToAlign: () => void; + scrollToBringIntoViewport: () => void; }, {} > { @@ -210,7 +210,7 @@ class SimpleBlobRenderer extends React.Component< context={simpleModel.context} opts={simpleModel.opts} savedHeight={simpleModel.savedHeight} - scrollToAlign={this.props.scrollToAlign} + scrollToBringIntoViewport={this.props.scrollToBringIntoViewport} />
); diff --git a/src/view/code.tsx b/src/view/code.tsx index 649cdf7f8d..87f2971aec 100644 --- a/src/view/code.tsx +++ b/src/view/code.tsx @@ -16,7 +16,7 @@ class SourceCodeRenderer extends React.Component< context: RendererContext; opts: RendererOpts; savedHeight: number; - scrollToAlign: () => void; + scrollToBringIntoViewport: () => void; }, {} > { @@ -25,8 +25,9 @@ class SourceCodeRenderer extends React.Component< languages: OV = mobx.observable.box([]); selectedLanguage: OV = mobx.observable.box(""); isFullWindow: OV = mobx.observable.box(false); // load this from opts - + editorHeight: OV = mobx.observable.box(this.props.savedHeight); // load this from opts editorRef; + resizeObserver; constructor(props) { super(props); this.editorRef = React.createRef(); @@ -54,6 +55,7 @@ class SourceCodeRenderer extends React.Component< this.language.set(detectedLanguage.id); } } + this.setEditorHeight(); }; handleLanguageChange = (event) => { @@ -70,7 +72,28 @@ class SourceCodeRenderer extends React.Component< toggleFit = () => { this.isFullWindow.set(!this.isFullWindow.get()); - setTimeout(() => this.props.scrollToAlign(), 10); + this.setEditorHeight(); + setTimeout(() => this.props.scrollToBringIntoViewport(), 10); + }; + + handleEditorChange = (value, event) => { + // editing will always be in fullscreen + this.isFullWindow.set(true); + this.code.set(value); + this.setEditorHeight(); + setTimeout(() => this.props.scrollToBringIntoViewport(), 10); + }; + + setEditorHeight = () => { + const fullWindowHeight = parseInt(this.props.opts.maxSize.height); + let _editorHeight = fullWindowHeight; + console.log(`this.isFullWindow.get() = ${this.isFullWindow.get()} and _editorHeight = ${_editorHeight}`); + if (!this.isFullWindow.get()) { + console.log(`recalculating _editorHeight`); + const noOfLines = this.code.get().split("\n").length; + _editorHeight = Math.min(noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10, fullWindowHeight); + } + this.editorHeight.set(_editorHeight); }; render() { @@ -80,19 +103,12 @@ class SourceCodeRenderer extends React.Component< if (!code) { return
; } - const fullWindowHeight = parseInt(opts.maxSize.height); - let editorHeight = fullWindowHeight; - if (!this.isFullWindow.get()) { - const noOfLines = code.split("\n").length; - editorHeight = Math.min(noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10, fullWindowHeight); - } - return (
From 37c3162b734e69794e4c292fcc6ff994a74ed358 Mon Sep 17 00:00:00 2001 From: Amarsh Anand Date: Wed, 30 Aug 2023 11:32:22 -0700 Subject: [PATCH 3/4] starting on save now --- src/prompt.less | 3 +++ src/view/code.tsx | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/prompt.less b/src/prompt.less index 92643e264f..972a5a61de 100644 --- a/src/prompt.less +++ b/src/prompt.less @@ -264,6 +264,9 @@ input[type="checkbox"] { line-height: 15px; text-align: center; } + section { + transition: height 0.3s ease-in-out; + } } .renderer-container.json-renderer { diff --git a/src/view/code.tsx b/src/view/code.tsx index 87f2971aec..5601c8e056 100644 --- a/src/view/code.tsx +++ b/src/view/code.tsx @@ -73,7 +73,7 @@ class SourceCodeRenderer extends React.Component< toggleFit = () => { this.isFullWindow.set(!this.isFullWindow.get()); this.setEditorHeight(); - setTimeout(() => this.props.scrollToBringIntoViewport(), 10); + setTimeout(() => this.props.scrollToBringIntoViewport(), 350); }; handleEditorChange = (value, event) => { @@ -81,7 +81,7 @@ class SourceCodeRenderer extends React.Component< this.isFullWindow.set(true); this.code.set(value); this.setEditorHeight(); - setTimeout(() => this.props.scrollToBringIntoViewport(), 10); + setTimeout(() => this.props.scrollToBringIntoViewport(), 350); }; setEditorHeight = () => { From dd39843983e3535f434919c106ccd35e69f89214 Mon Sep 17 00:00:00 2001 From: Amarsh Anand Date: Thu, 31 Aug 2023 19:01:13 -0700 Subject: [PATCH 4/4] got save to display message. now need to intergrate with save() handler --- src/linecomps.tsx | 1 - src/prompt.less | 19 +++++- src/simplerenderer.tsx | 3 +- src/view/code.tsx | 143 +++++++++++++++++++++++++++------------ src/view/code_mobx.tsx | 149 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 269 insertions(+), 46 deletions(-) create mode 100644 src/view/code_mobx.tsx diff --git a/src/linecomps.tsx b/src/linecomps.tsx index ac2d1b2a30..ffb4699c10 100644 --- a/src/linecomps.tsx +++ b/src/linecomps.tsx @@ -600,7 +600,6 @@ class LineCmd extends React.Component< } scrollToBringIntoViewport = () => { - console.log(`here`); const container = document.getElementsByClassName("lines")[0]; const targetDiv = this.lineRef.current; const targetPosition = targetDiv.getBoundingClientRect(); diff --git a/src/prompt.less b/src/prompt.less index 972a5a61de..0d15004685 100644 --- a/src/prompt.less +++ b/src/prompt.less @@ -241,7 +241,7 @@ input[type="checkbox"] { .dropdown { background: #dbdbdb; color: black; - border-radius: 4px 4px 0 0; + border-radius: 6px 6px 0 0; font-size: 10px; padding: 2px 0 5px 5px; outline: none; @@ -267,6 +267,23 @@ input[type="checkbox"] { section { transition: height 0.3s ease-in-out; } + .save-enabled { + color: white; + background-color: #4e9a06; + } + .save-disabled { + color: rgb(52, 52, 52); + background-color: #aaaea7; + cursor: default !important; + } + .error { + background-color: red; + color: white; + border-radius: 6px; + margin-bottom: 1rem; + padding: 4px 1rem; + max-width: 16rem; + } } .renderer-container.json-renderer { diff --git a/src/simplerenderer.tsx b/src/simplerenderer.tsx index 689f4ba05e..eb76cb9d0a 100644 --- a/src/simplerenderer.tsx +++ b/src/simplerenderer.tsx @@ -200,12 +200,13 @@ class SimpleBlobRenderer extends React.Component< } let dataBlob = new Blob([model.ptyData.data]); let simpleModel = model as SimpleBlobRendererModel; - let { festate, cmdstr } = this.props.initParams.rawCmd; + let { festate, cmdstr, exitcode } = this.props.initParams.rawCmd; return (
= mobx.IObservableValue; - -@mobxReact.observer class SourceCodeRenderer extends React.Component< { data: Blob; cmdstr: String; cwd: String; + exitcode: Number; context: RendererContext; opts: RendererOpts; savedHeight: number; @@ -20,39 +15,59 @@ class SourceCodeRenderer extends React.Component< }, {} > { - code: OV = mobx.observable.box(""); - language: OV = mobx.observable.box(""); - languages: OV = mobx.observable.box([]); - selectedLanguage: OV = mobx.observable.box(""); - isFullWindow: OV = mobx.observable.box(false); // load this from opts - editorHeight: OV = mobx.observable.box(this.props.savedHeight); // load this from opts - editorRef; - resizeObserver; + /** + * codeCache is a Hashmap with key=filepath and value=code + * Editor should never read the code directly from the filesystem. it should read from the cache. + * Upon loading a file (props.data contains the file-contents) FOR THE FIRST TIME, + * we will put it in the cache, and will update the contents of the cache upon every onChange(). + * ALl this is to ensure that the file contents doesnt get reloaded when the line scrolls out of the viewport + * (and hence the react component gets destroyed) + */ + static codeCache = new Map(); + + filePath; constructor(props) { super(props); this.editorRef = React.createRef(); + this.state = { + code: "", + language: "", + languages: [], + selectedLanguage: "", + isFullWindow: false, + isSave: false, + editorHeight: props.savedHeight, + errorMessage: null, + }; } - componentDidMount() { - let prtn = this.props.data.text(); - prtn.then((text) => this.code.set(text)); + componentDidMount(): void { + // DANGEROUS ... I AM ASSUMING THE COMMAND IS IN FORMAT cat prompt_samples/sample.java + // filePath should be saved in the new lineOpts field that Mike is working on :) + this.filePath = `${this.props.cwd}/${this.props.cmdstr.split(" ")[1]}`; + const code = SourceCodeRenderer.codeCache.get(this.filePath); + if (code) { + this.setState({ code }); + } else + this.props.data.text().then((code) => { + this.setState({ code }); + SourceCodeRenderer.codeCache.set(this.filePath, code); + }); } handleEditorDidMount = (editor, monaco) => { - // Use a regular expression to match a filename with an extension const extension = this.props.cmdstr.match(/(?:[^\\\/:*?"<>|\r\n]+\.)([a-zA-Z0-9]+)\b/)?.[1] || ""; const detectedLanguage = monaco.languages .getLanguages() - .find((lang) => lang.extensions && lang.extensions.includes("." + extension)); + .find((lang) => lang.extensions?.includes("." + extension)); const languages = monaco.languages.getLanguages().map((lang) => lang.id); - this.languages.set(languages); + this.setState({ languages }); if (detectedLanguage) { - this.selectedLanguage.set(detectedLanguage.id); this.editorRef.current = editor; const model = editor.getModel(); if (model) { monaco.editor.setModelLanguage(model, detectedLanguage.id); - this.language.set(detectedLanguage.id); + this.setState({ selectedLanguage: detectedLanguage.id, language: detectedLanguage.id }); } } this.setEditorHeight(); @@ -60,55 +75,77 @@ class SourceCodeRenderer extends React.Component< handleLanguageChange = (event) => { const selectedLanguage = event.target.value; - this.selectedLanguage.set(selectedLanguage); + this.setState({ selectedLanguage }); if (this.editorRef.current) { const model = this.editorRef.current.getModel(); if (model) { monaco.editor.setModelLanguage(model, selectedLanguage); - this.language.set(selectedLanguage); + this.setState({ language: selectedLanguage }); } } }; toggleFit = () => { - this.isFullWindow.set(!this.isFullWindow.get()); + const isFullWindow = !this.state.isFullWindow; + this.setState({ isFullWindow }); this.setEditorHeight(); setTimeout(() => this.props.scrollToBringIntoViewport(), 350); }; - handleEditorChange = (value, event) => { - // editing will always be in fullscreen - this.isFullWindow.set(true); - this.code.set(value); + doSave = () => { + // call the function that would save the file to filesystem. would likely be async + // as a result of the save operation, the entire component should get reloaded (** HOW **) + // once its reloaded, this.props.data.text() should contain the latest code + // in which case, the cache will get refilled and we can consider the transaction "committed" + this.setState({ errorMessage: "File could not be saved" }); + setTimeout(() => this.setState({ errorMessage: null }), 3000); + }; + + handleEditorChange = (code) => { + this.setState({ isFullWindow: true, code }); + SourceCodeRenderer.codeCache.set(this.filePath, code); this.setEditorHeight(); setTimeout(() => this.props.scrollToBringIntoViewport(), 350); + this.props.data.text().then((originalCode) => this.setState({ isSave: code !== originalCode })); }; setEditorHeight = () => { const fullWindowHeight = parseInt(this.props.opts.maxSize.height); let _editorHeight = fullWindowHeight; - console.log(`this.isFullWindow.get() = ${this.isFullWindow.get()} and _editorHeight = ${_editorHeight}`); - if (!this.isFullWindow.get()) { - console.log(`recalculating _editorHeight`); - const noOfLines = this.code.get().split("\n").length; + if (!this.state.isFullWindow) { + const noOfLines = this.state.code.split("\n").length; _editorHeight = Math.min(noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10, fullWindowHeight); } - this.editorHeight.set(_editorHeight); + this.setState({ editorHeight: _editorHeight }); }; render() { - let opts = this.props.opts; - let lang = this.language.get(); - let code = this.code.get(); - if (!code) { + const { opts, exitcode } = this.props; + const { lang, code, isSave } = this.state; + + if (!code) return
; - } + + if (exitcode === 1) + return ( +
+ {code} +
+ ); + return (
- {this.isFullWindow.get() ? `shrink` : `expand`} + {this.state.isFullWindow ? `shrink` : `expand`}
+ {!this.props.opts.readOnly && ( +
+
+ {"save"} +
+
+ )}
+ {this.state.errorMessage && ( +
+
+ {this.state.errorMessage} +
+
+ )}
); } diff --git a/src/view/code_mobx.tsx b/src/view/code_mobx.tsx new file mode 100644 index 0000000000..41a0c350c6 --- /dev/null +++ b/src/view/code_mobx.tsx @@ -0,0 +1,149 @@ +import * as React from "react"; +import * as mobx from "mobx"; +import * as mobxReact from "mobx-react"; +import { RendererContext, RendererOpts } from "../types"; +import Editor from "@monaco-editor/react"; +import { GlobalModel } from "../model"; + +type OV = mobx.IObservableValue; + +@mobxReact.observer +class SourceCodeRenderer extends React.Component< + { + data: Blob; + cmdstr: String; + cwd: String; + context: RendererContext; + opts: RendererOpts; + savedHeight: number; + scrollToBringIntoViewport: () => void; + }, + {} +> { + code: OV = mobx.observable.box(""); + language: OV = mobx.observable.box(""); + languages: OV = mobx.observable.box([]); + selectedLanguage: OV = mobx.observable.box(""); + isFullWindow: OV = mobx.observable.box(false); // load this from opts + editorHeight: OV = mobx.observable.box(this.props.savedHeight); // load this from opts + editorRef; + resizeObserver; + constructor(props) { + super(props); + this.editorRef = React.createRef(); + console.log(`resetting the code`); + this.props.data.text().then((text) => this.code.set(text)); + } + + componentDidMount() {} + + componentWillUnmount(): void { + console.log(`will unmount`); + } + + handleEditorDidMount = (editor, monaco) => { + // Use a regular expression to match a filename with an extension + const extension = this.props.cmdstr.match(/(?:[^\\\/:*?"<>|\r\n]+\.)([a-zA-Z0-9]+)\b/)?.[1] || ""; + const detectedLanguage = monaco.languages + .getLanguages() + .find((lang) => lang.extensions && lang.extensions.includes("." + extension)); + const languages = monaco.languages.getLanguages().map((lang) => lang.id); + this.languages.set(languages); + if (detectedLanguage) { + this.selectedLanguage.set(detectedLanguage.id); + this.editorRef.current = editor; + const model = editor.getModel(); + if (model) { + monaco.editor.setModelLanguage(model, detectedLanguage.id); + this.language.set(detectedLanguage.id); + } + } + this.setEditorHeight(); + }; + + handleLanguageChange = (event) => { + const selectedLanguage = event.target.value; + this.selectedLanguage.set(selectedLanguage); + if (this.editorRef.current) { + const model = this.editorRef.current.getModel(); + if (model) { + monaco.editor.setModelLanguage(model, selectedLanguage); + this.language.set(selectedLanguage); + } + } + }; + + toggleFit = () => { + this.isFullWindow.set(!this.isFullWindow.get()); + this.setEditorHeight(); + setTimeout(() => this.props.scrollToBringIntoViewport(), 350); + }; + + handleEditorChange = (value, event) => { + // editing will always be in fullscreen + this.isFullWindow.set(true); + this.code.set(value); + this.setEditorHeight(); + setTimeout(() => this.props.scrollToBringIntoViewport(), 350); + }; + + setEditorHeight = () => { + const fullWindowHeight = parseInt(this.props.opts.maxSize.height); + let _editorHeight = fullWindowHeight; + if (!this.isFullWindow.get()) { + const noOfLines = this.code.get().split("\n").length; + _editorHeight = Math.min(noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10, fullWindowHeight); + } + this.editorHeight.set(_editorHeight); + }; + + render() { + let opts = this.props.opts; + let lang = this.language.get(); + let code = this.code.get(); + if (!code) { + return
; + } + return ( +
+
+ +
+
+ +
+
+ {this.isFullWindow.get() ? `shrink` : `expand`} +
+
+
+
+ ); + } +} + +export { SourceCodeRenderer };