Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/linecomps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,33 @@ class LineCmd extends React.Component<
};
}

scrollToBringIntoViewport = () => {
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;
Expand Down Expand Up @@ -734,6 +761,7 @@ class LineCmd extends React.Component<
plugin={rendererPlugin}
onHeightChange={this.handleHeightChange}
initParams={this.makeRendererModelInitializeParams()}
scrollToBringIntoViewport={this.scrollToBringIntoViewport}
/>
</If>
<If condition={rendererPlugin != null && rendererPlugin.rendererType == "full"}>
Expand Down
38 changes: 34 additions & 4 deletions src/prompt.less
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,11 @@ input[type="checkbox"] {
}

.dropdown {
background: rgb(180, 180, 180);
background: #dbdbdb;
color: black;
border-radius: 4px 4px 0 0;
border-radius: 6px 6px 0 0;
font-size: 10px;
font-family: system-ui;
padding: 1px 0 3px 3px;
padding: 2px 0 5px 5px;
outline: none;
}
}
Expand All @@ -254,6 +253,37 @@ 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;
}
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 {
Expand Down
5 changes: 4 additions & 1 deletion src/simplerenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class SimpleBlobRenderer extends React.Component<
plugin: RendererPluginType;
onHeightChange: () => void;
initParams: RendererModelInitializeParams;
scrollToBringIntoViewport: () => void;
},
{}
> {
Expand Down Expand Up @@ -262,17 +263,19 @@ class SimpleBlobRenderer extends React.Component<
<div ref={this.wrapperDivRef}>(no component found in plugin)</div>;
}
let simpleModel = model as SimpleBlobRendererModel;
let { festate, cmdstr } = this.props.initParams.rawCmd;
let { festate, cmdstr, exitcode } = this.props.initParams.rawCmd;
return (
<div ref={this.wrapperDivRef}>
<Comp
cwd={festate.cwd}
cmdstr={cmdstr}
exitcode={exitcode}
data={simpleModel.dataBlob}
lineState={simpleModel.lineState}
context={simpleModel.context}
opts={simpleModel.opts}
savedHeight={simpleModel.savedHeight}
scrollToBringIntoViewport={this.props.scrollToBringIntoViewport}
/>
</div>
);
Expand Down
157 changes: 123 additions & 34 deletions src/view/code.tsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,153 @@
import * as React from "react";
import * as mobx from "mobx";
import * as mobxReact from "mobx-react";
import { RendererContext, RendererOpts, LineStateType } from "../types";
import Editor from "@monaco-editor/react";
import { GlobalModel } from "../model";

type OV<V> = mobx.IObservableValue<V>;

@mobxReact.observer
class SourceCodeRenderer extends React.Component<
{
data: Blob;
cmdstr: String;
cwd: String;
exitcode: Number;
context: RendererContext;
opts: RendererOpts;
savedHeight: number;
scrollToBringIntoViewport: () => void;
lineState: LineStateType;
},
{}
> {
code: OV<string> = mobx.observable.box("");
language: OV<string> = mobx.observable.box("");
languages: OV<string[]> = mobx.observable.box([]);
selectedLanguage: OV<string> = mobx.observable.box("");
/**
* 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();

editorRef;
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();
};

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 = () => {
const isFullWindow = !this.state.isFullWindow;
this.setState({ isFullWindow });
this.setEditorHeight();
setTimeout(() => this.props.scrollToBringIntoViewport(), 350);
};

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;
if (!this.state.isFullWindow) {
const noOfLines = this.state.code.split("\n").length;
_editorHeight = Math.min(noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10, fullWindowHeight);
}
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 <div className="renderer-container code-renderer" style={{ height: this.props.savedHeight }} />;
}
const noOfLines = code.split("\n").length;
const editorHeight = Math.min(
noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10,
parseInt(opts.maxSize.height)
);

if (exitcode === 1)
return (
<div
className="renderer-container code-renderer"
style={{
fontSize: GlobalModel.termFontSize.get(),
fontFamily: "JetBrains Mono",
color: "white",
}}
>
{code}
</div>
);

return (
<div className="renderer-container code-renderer">
<div className="scroller" style={{ maxHeight: opts.maxSize.height, paddingBottom: "15px" }}>
<Editor
theme="hc-black"
height={editorHeight}
height={this.state.editorHeight}
defaultLanguage={lang}
defaultValue={code}
onMount={this.handleEditorDidMount}
Expand All @@ -94,22 +157,48 @@ class SourceCodeRenderer extends React.Component<
fontFamily: "JetBrains Mono",
readOnly: this.props.opts.readOnly,
}}
onChange={this.handleEditorChange}
/>
</div>
<div style={{ position: "absolute", bottom: "-3px", right: 0 }}>
<select
className="dropdown"
value={this.selectedLanguage.get()}
value={this.state.selectedLanguage}
onChange={this.handleLanguageChange}
style={{ maxWidth: "5rem", marginRight: "24px" }}
style={{ minWidth: "6rem", maxWidth: "6rem", marginRight: "8px" }}
>
{this.languages.get().map((lang, index) => (
{this.state.languages.map((lang, index) => (
<option key={index} value={lang}>
{lang}
</option>
))}
</select>
<div className="cmd-hints" style={{ minWidth: "6rem", maxWidth: "6rem" }}>
<div onClick={this.toggleFit} className="hint-item color-white">
{this.state.isFullWindow ? `shrink` : `expand`}
</div>
</div>
{!this.props.opts.readOnly && (
<div className="cmd-hints" style={{ minWidth: "6rem", maxWidth: "6rem", marginLeft: "-18px" }}>
<div
onClick={this.doSave}
className={`hint-item ${isSave ? "save-enabled" : "save-disabled"}`}
>
{"save"}
</div>
</div>
)}
</div>
{this.state.errorMessage && (
<div style={{ position: "absolute", bottom: "-3px", left: "14px" }}>
<div
className="error"
style={{ fontSize: GlobalModel.termFontSize.get(), fontFamily: "JetBrains Mono" }}
>
{this.state.errorMessage}
</div>
</div>
)}
</div>
);
}
Expand Down
Loading