Skip to content
Open
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
76 changes: 69 additions & 7 deletions apps/vscode/src/providers/cell/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ import { ExtensionHost } from "../../host";
import { hasHooks } from "../../host/hooks";
import { isKnitrDocument } from "../../host/executors";
import { commands } from "vscode";
import { virtualDocForCode, withVirtualDocUri } from "../../vdoc/vdoc";
import { embeddedLanguage } from "../../vdoc/languages";
import { Uri } from "vscode";
import { StatementRange } from "positron";

export function cellCommands(host: ExtensionHost, engine: MarkdownEngine): Command[] {
return [
Expand Down Expand Up @@ -336,34 +340,92 @@ class RunCurrentCommand extends RunCommand implements Command {
let selection = context.selectedText;
const activeBlock = context.blocks.find(block => block.active);

// idea: first check that we are in Positron
// and leave the others untouched

// if the selection is empty and this isn't a knitr document then it resolves to run cell
if (selection.length <= 0 && !isKnitrDocument(editor.document, this.engine_)) {
if (false) {
if (activeBlock) {
const executor = await this.cellExecutorForLanguage(activeBlock.language, editor.document, this.engine_);
if (executor) {
await executeInteractive(executor, [activeBlock.code], editor.document);
await activateIfRequired(editor);
}
}

} else {
// if the selection is empty take the whole line, otherwise take the selected text exactly
let action: CodeViewSelectionAction | undefined;
if (selection.length <= 0) {
if (activeBlock) {
selection = lines(activeBlock.code)[context.selection.start.line];

// if the selection is empty and we are in Positron:
// try to get the statement's range and use that as the selection
if (selection.length <= 0 && activeBlock && hasHooks()) {
const codeLines = lines(activeBlock.code)
const vdoc = virtualDocForCode(codeLines, embeddedLanguage(activeBlock.language)!);
if (vdoc) {
const parentUri = Uri.file(editor.document.fileName);
const injectedLines = (vdoc.language?.inject?.length ?? 0)

const positionIntoVdoc = (p: { line: number, character: number }) =>
new Position(p.line + injectedLines, p.character)
const positionOutOfVdoc = (p: { line: number, character: number }) =>
new Position(p.line - injectedLines, p.character)

const result = await withVirtualDocUri(vdoc, parentUri, "statementRange", async (uri) => {
return await commands.executeCommand<StatementRange>(
"vscode.executeStatementRangeProvider",
uri,
positionIntoVdoc(context.selection.start)
);
});
const { range, code } = result
if (code === undefined) return
const adjustedEnd = positionOutOfVdoc(range.end)

selection = code
action = "nextline";

// BEGIN ref: https://github.com/posit-dev/positron/blob/main/src/vs/workbench/contrib/positronConsole/browser/positronConsoleActions.ts#L428
// strategy from Positron using `StatementRangeProvider` to find range of next statement
// and move cursor based on that.
if (adjustedEnd.line + 1 <= codeLines.length) {
const nextStatementRange = await withVirtualDocUri(vdoc, parentUri, "statementRange", async (uri) => {
return await commands.executeCommand<StatementRange>(
"vscode.executeStatementRangeProvider",
uri,
positionIntoVdoc(new Position(adjustedEnd.line + 1, 1)) // look for statement at line after current statement
);
});
const nextStatement = {
start: positionOutOfVdoc(nextStatementRange.range.start),
end: positionOutOfVdoc(nextStatementRange.range.end)
};
if (nextStatement.start.line > adjustedEnd.line) {
action = nextStatement.start
// the nextStatement may start before & end after the current statement if e.g. inside a function:
} else if (nextStatement.end.line > adjustedEnd.line) {
action = nextStatement.end
}
}
// END ref.
}
}

// if the selection is still empty:
// take the whole line as the selection
if (selection.length <= 0 && activeBlock) {
selection = lines(activeBlock.code)[context.selection.start.line];
action = "nextline";
}

// run code
const executor = await this.cellExecutorForLanguage(context.activeLanguage, editor.document, this.engine_);
if (executor) {
await executeInteractive(executor, [selection], editor.document);

// advance cursor if necessary
//
if (action) {
editor.setBlockSelection(context, "nextline");
console.log('action!!!', action, this.id)
await editor.setBlockSelection(context, action);
}
}
}
Expand Down
22 changes: 11 additions & 11 deletions packages/editor-codemirror/src/behaviors/trackselection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { DispatchEvent, codeViewCellContext, kCodeViewNextLineTransaction } from
import { Behavior, BehaviorContext, State } from ".";

// track the selection in prosemirror
export function trackSelectionBehavior(context: BehaviorContext) : Behavior {
export function trackSelectionBehavior(context: BehaviorContext): Behavior {

let unsubscribe: VoidFunction;

Expand All @@ -50,32 +50,32 @@ export function trackSelectionBehavior(context: BehaviorContext) : Behavior {
unsubscribe = context.pmContext.events.subscribe(DispatchEvent, (tr: Transaction | undefined) => {
if (tr) {
// track selection changes that occur when we don't have focus
if (!cmView.hasFocus && tr.selectionSet && !tr.docChanged && (tr.selection instanceof TextSelection)) {
if (tr.selectionSet && !tr.docChanged && (tr.selection instanceof TextSelection)) {
const cmSelection = asCodeMirrorSelection(context.view, cmView, context.getPos);
context.withState(State.Updating, () => {
if (cmSelection) {
cmView.dispatch({ selection: cmSelection });
} else {
cmView.dispatch({ selection: EditorSelection.single(0)})
}
cmView.dispatch({ selection: EditorSelection.single(0) })
}
})
} else if (tr.getMeta(kCodeViewNextLineTransaction) === true) {
// NOTE: this is a special directive to advance to the next line. as distinct
// from the block above it is not a reporting of a change in the PM selection
// but rather an instruction to move the CM selection to the next line. as
// but rather an instruction to move the CM selection to the next line. as
// such we do not encose the code in State.Updating, because we want an update
// to the PM selection to occur
const cmSelection = asCodeMirrorSelection(context.view, cmView, context.getPos);
if (cmSelection) {
if (cursorLineDown(cmView)) {
cursorLineStart(cmView);
}
}
}
// for other selection changes
// for other selection changes
} else if (cmView.hasFocus && tr.selectionSet && (tr.selection instanceof TextSelection)) {
codeViewAssist();
}
}
}
});
},

Expand All @@ -91,7 +91,7 @@ export const asCodeMirrorSelection = (
cmView: EditorView,
getPos: (() => number) | boolean
) => {
if (typeof(getPos) === "function") {
if (typeof (getPos) === "function") {
const offset = getPos() + 1;
const node = pmView.state.doc.nodeAt(getPos());
if (node) {
Expand All @@ -104,8 +104,8 @@ export const asCodeMirrorSelection = (
} else if (selection.from <= cmRange.from && selection.to >= cmRange.to) {
return EditorSelection.single(0, cmView.state.doc.length);
}

}
}
return undefined;
}
}
2 changes: 1 addition & 1 deletion packages/editor-types/src/codeview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface CodeViewActiveBlockContext {
selectedText: string;
}

export type CodeViewSelectionAction = "nextline" | "nextblock" | "prevblock";
export type CodeViewSelectionAction = "nextline" | "nextblock" | "prevblock" | { line: number, character: number };

export interface CodeViewCellContext {
filepath: string;
Expand Down
22 changes: 16 additions & 6 deletions packages/editor/src/api/codeview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,25 @@ export function codeViewSetBlockSelection(
context: CodeViewActiveBlockContext,
action: CodeViewSelectionAction
) {


const activeIndex = context.blocks.findIndex(block => block.active);

if (activeIndex !== -1) {
if (action === "nextline") {
if (typeof action === 'object') {
// convert action line and character in code block space to pos in prosemirror space
const block = context.blocks[activeIndex]
// asummes the meta line looks like this:
const metaLine = '{' + block.language + '}\n'
const code = lines(block.code)
if (action.line > code.length) throw 'trying to move cursor outside block!'
let pos = block.pos + metaLine.length
for (let i = 0; i < action.line; i++) {
pos += code[i].length + 1
}
pos += action.character

navigateToPos(view, pos, false)
}
else if (action === "nextline") {
const tr = view.state.tr;
tr.setMeta(kCodeViewNextLineTransaction, true);
view.dispatch(tr);
Expand All @@ -226,9 +239,6 @@ export function codeViewSetBlockSelection(
}
}
}



}


Expand Down