Skip to content

Commit

Permalink
Merge pull request #70 from Code-Inspect/46-diff-viewer-for-sliced-code
Browse files Browse the repository at this point in the history
 Diff viewer for sliced code
  • Loading branch information
EagleoutIce authored Mar 14, 2024
2 parents e4570ee + f9b731f commit b14038d
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 71 deletions.
3 changes: 2 additions & 1 deletion example/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"vscode-flowr.verboseLog": true
"vscode-flowr.verboseLog": true,
"vscode-flowr.style.sliceDisplay": "diff"
}
28 changes: 28 additions & 0 deletions example/example-large.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
library(cranlogs)

# retrieve top N packages
N <- 500

# Get the names of all packages
pkgnames <- available.packages(repos="https://cloud.r-project.org")[,1]

# init result
download_numbers <- data.frame()

stepsize <- 750
for (i in seq(1, length(pkgnames), by = stepsize)) {
start <- i
end <- min(i+stepsize-1, length(pkgnames))
cat("Retrieving download numbers for packages", start, "to", end, "from total", length(pkgnames), "\n")
data <- cran_downloads(packages = pkgnames[start:end], when="last-month")
download_numbers <- rbind(download_numbers, data)
}

# now packages appear often as they are with different days in the data, therefore we aggregate
download_numbers <- aggregate(count ~ package, data=download_numbers, sum)

# sort download numbers
download_numbers <- download_numbers[order(-download_numbers$count),]
print(download_numbers[1:N,])

write.table(download_numbers[1:N,], file="top-r-downloads.txt", quote=FALSE, sep=",", row.names=FALSE, col.names=FALSE)
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,19 @@
"maximum": 1,
"markdownDescription": "The opacity that code which isn't part of the current slice should have, between 0 (invisible) and 1 (opaque)."
},
"vscode-flowr.style.sliceDisplay": {
"type": "string",
"default": "text",
"enum": [
"text",
"diff"
],
"enumDescriptions": [
"Code that isn't part of the current slice will be grayed out in the active editor.",
"Open a diff view that shows the current slice and the rest of the file."
],
"markdownDescription": "The way that slices should be displayed."
},
"vscode-flowr.verboseLog": {
"type": "boolean",
"default": false,
Expand Down
64 changes: 4 additions & 60 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,23 @@
import * as vscode from 'vscode'
import { FlowrInternalSession } from './flowr/internal-session'
import { FlowrServerSession } from './flowr/server-session'
import type { NodeId } from '@eagleoutice/flowr'
import type { SourceRange } from '@eagleoutice/flowr/util/range'
import { Settings } from './settings'
import { registerSliceCommands } from './slice'

export const MINIMUM_R_MAJOR = 3
export const BEST_R_MAJOR = 4

export let flowrSession: FlowrInternalSession | FlowrServerSession | undefined
export let outputChannel: vscode.OutputChannel
export let sliceDecoration: vscode.TextEditorDecorationType

let flowrStatus: vscode.StatusBarItem

export function activate(context: vscode.ExtensionContext) {
console.log('Loading vscode-flowr')

outputChannel = vscode.window.createOutputChannel('flowR')
recreateSliceDecorationType()

context.subscriptions.push(vscode.commands.registerCommand('vscode-flowr.slice.cursor', async() => {
const activeEditor = vscode.window.activeTextEditor
if(activeEditor?.selection) {
if(!flowrSession) {
await establishInternalSession()
}
void flowrSession?.retrieveSlice(activeEditor.selection.active, activeEditor, true)
}
}))
context.subscriptions.push(vscode.commands.registerCommand('vscode-flowr.slice.clear', () => {
const activeEditor = vscode.window.activeTextEditor
if(activeEditor) {
activeEditor.setDecorations(sliceDecoration, [])
}
}))
context.subscriptions.push(vscode.commands.registerCommand('vscode-flowr.slice.cursor-reconstruct', async() => {
const activeEditor = vscode.window.activeTextEditor
if(activeEditor) {
if(!flowrSession) {
await establishInternalSession()
}
const code = await flowrSession?.retrieveSlice(activeEditor.selection.active, activeEditor, false)
const doc = await vscode.workspace.openTextDocument({language: 'r', content: code})
void vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside)
}
}))

registerSliceCommands(context)

context.subscriptions.push(vscode.commands.registerCommand('vscode-flowr.session.connect', () => {
establishServerSession()
Expand All @@ -64,16 +36,7 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(flowrStatus)
updateStatusBar()

vscode.workspace.onDidChangeConfiguration(e => {
if(e.affectsConfiguration(`${Settings.Category}.${Settings.StyleSliceOpacity}`)) {
recreateSliceDecorationType()
}
})

context.subscriptions.push(new vscode.Disposable(() => {
sliceDecoration.dispose()
destroySession()
}))
context.subscriptions.push(new vscode.Disposable(() => destroySession()))
process.on('SIGINT', () => destroySession())

if(getConfig().get<boolean>(Settings.ServerAutoConnect)) {
Expand Down Expand Up @@ -121,22 +84,3 @@ export function updateStatusBar() {
flowrStatus.hide()
}
}

export function createSliceDecorations(document: vscode.TextDocument, sliceElements: { id: NodeId, location: SourceRange }[]): vscode.DecorationOptions[]{
// create a set to make finding matching lines
const sliceLines = new Set<number>(sliceElements.map(s => s.location.start.line - 1))
const ret: vscode.DecorationOptions[] = []
for(let i = 0; i < document.lineCount; i++) {
if(!sliceLines.has(i)) {
ret.push({range: new vscode.Range(i, 0, i, document.lineAt(i).text.length)})
}
}
return ret
}

function recreateSliceDecorationType() {
sliceDecoration?.dispose()
sliceDecoration = vscode.window.createTextEditorDecorationType({
opacity: getConfig().get<number>(Settings.StyleSliceOpacity)?.toString()
})
}
13 changes: 7 additions & 6 deletions src/flowr/internal-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import type { NodeId, RShellOptions, SingleSlicingCriterion} from '@eagleoutice/
import { LAST_STEP, requestFromInput, RShell, SteppingSlicer } from '@eagleoutice/flowr'
import type { SourceRange } from '@eagleoutice/flowr/util/range'
import { isNotUndefined } from '@eagleoutice/flowr/util/assert'
import { BEST_R_MAJOR, MINIMUM_R_MAJOR, createSliceDecorations, getConfig, isVerbose, sliceDecoration, updateStatusBar } from '../extension'
import { BEST_R_MAJOR, MINIMUM_R_MAJOR, getConfig, isVerbose, updateStatusBar } from '../extension'
import { Settings } from '../settings'
import { displaySlice } from '../slice'

export class FlowrInternalSession {

Expand Down Expand Up @@ -75,12 +76,12 @@ export class FlowrInternalSession {
this.shell?.close()
}

async retrieveSlice(pos: vscode.Position, editor: vscode.TextEditor, decorate: boolean): Promise<string> {
async retrieveSlice(pos: vscode.Position, editor: vscode.TextEditor, display: boolean): Promise<string> {
if(!this.shell) {
return ''
}
try {
return await this.extractSlice(this.shell, editor, pos, decorate)
return await this.extractSlice(this.shell, editor, pos, display)
} catch(e) {
this.outputChannel.appendLine('Error: ' + (e as Error)?.message);
(e as Error).stack?.split('\n').forEach(l => this.outputChannel.appendLine(l))
Expand All @@ -89,7 +90,7 @@ export class FlowrInternalSession {
}
}

private async extractSlice(shell: RShell, editor: vscode.TextEditor, pos: vscode.Position, decorate: boolean): Promise<string> {
private async extractSlice(shell: RShell, editor: vscode.TextEditor, pos: vscode.Position, display: boolean): Promise<string> {
const filename = editor.document.fileName
const content = FlowrInternalSession.fixEncoding(editor.document.getText())

Expand Down Expand Up @@ -119,8 +120,8 @@ export class FlowrInternalSession {
return a.location.start.line - b.location.start.line || a.location.start.column - b.location.start.column
})

if(decorate) {
editor.setDecorations(sliceDecoration, createSliceDecorations(editor.document, sliceElements))
if(display) {
void displaySlice(editor, sliceElements)
}
if(isVerbose()) {
this.outputChannel.appendLine('slice: ' + JSON.stringify([...result.slice.result]))
Expand Down
9 changes: 5 additions & 4 deletions src/flowr/server-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { visitAst } from '@eagleoutice/flowr'
import type { SourceRange } from '@eagleoutice/flowr/util/range'
import { isNotUndefined } from '@eagleoutice/flowr/util/assert'
import { FlowrInternalSession } from './internal-session'
import { createSliceDecorations, establishInternalSession, getConfig, isVerbose, sliceDecoration, updateStatusBar } from '../extension'
import { establishInternalSession, getConfig, isVerbose, updateStatusBar } from '../extension'
import type { FlowrHelloResponseMessage } from '@eagleoutice/flowr/cli/repl/server/messages/hello'
import { Settings } from '../settings'
import { displaySlice } from '../slice'

export class FlowrServerSession {

Expand Down Expand Up @@ -115,7 +116,7 @@ export class FlowrServerSession {
})
}

async retrieveSlice(pos: vscode.Position, editor: vscode.TextEditor, decorate: boolean): Promise<string> {
async retrieveSlice(pos: vscode.Position, editor: vscode.TextEditor, display: boolean): Promise<string> {
const filename = editor.document.fileName
const content = FlowrInternalSession.fixEncoding(editor.document.getText())

Expand Down Expand Up @@ -152,8 +153,8 @@ export class FlowrServerSession {
return a.location.start.line - b.location.start.line || a.location.start.column - b.location.start.column
})

if(decorate) {
editor.setDecorations(sliceDecoration, createSliceDecorations(editor.document, sliceElements))
if(display) {
void displaySlice(editor, sliceElements)
}
if(isVerbose()) {
this.outputChannel.appendLine('slice: ' + JSON.stringify([...sliceResponse.results.slice.result]))
Expand Down
3 changes: 3 additions & 0 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export enum Settings {
ServerAutoConnect = 'server.autoConnect',

StyleSliceOpacity = 'style.sliceOpacity',
StyleSliceDisplay = 'style.sliceDisplay',

Rexecutable = 'r.executable',
}

export type SliceDisplay = 'text' | 'diff'
79 changes: 79 additions & 0 deletions src/slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as vscode from 'vscode'
import type { NodeId } from '@eagleoutice/flowr'
import type { SourceRange } from '@eagleoutice/flowr/util/range'
import { establishInternalSession, flowrSession, getConfig } from './extension'
import type { SliceDisplay } from './settings'
import { Settings } from './settings'

export let sliceDecoration: vscode.TextEditorDecorationType

export function registerSliceCommands(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand('vscode-flowr.slice.cursor', async() => {
const activeEditor = vscode.window.activeTextEditor
if(activeEditor?.selection) {
if(!flowrSession) {
await establishInternalSession()
}
void flowrSession?.retrieveSlice(activeEditor.selection.active, activeEditor, true)
}
}))
context.subscriptions.push(vscode.commands.registerCommand('vscode-flowr.slice.clear', () => {
const activeEditor = vscode.window.activeTextEditor
if(activeEditor) {
activeEditor.setDecorations(sliceDecoration, [])
}
}))
context.subscriptions.push(vscode.commands.registerCommand('vscode-flowr.slice.cursor-reconstruct', async() => {
const activeEditor = vscode.window.activeTextEditor
if(activeEditor) {
if(!flowrSession) {
await establishInternalSession()
}
const code = await flowrSession?.retrieveSlice(activeEditor.selection.active, activeEditor, false)
const doc = await vscode.workspace.openTextDocument({language: 'r', content: code})
void vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside)
}
}))

recreateSliceDecorationType()
vscode.workspace.onDidChangeConfiguration(e => {
if(e.affectsConfiguration(`${Settings.Category}.${Settings.StyleSliceOpacity}`)) {
recreateSliceDecorationType()
}
})
context.subscriptions.push(new vscode.Disposable(() => sliceDecoration.dispose()))
}

export async function displaySlice(editor: vscode.TextEditor, sliceElements: { id: NodeId, location: SourceRange }[]) {
const sliceLines = new Set<number>(sliceElements.map(s => s.location.start.line - 1))
switch(getConfig().get<SliceDisplay>(Settings.StyleSliceDisplay)) {
case 'text': {
const decorations: vscode.DecorationOptions[] = []
for(let i = 0; i < editor.document.lineCount; i++) {
if(!sliceLines.has(i)) {
decorations.push({range: new vscode.Range(i, 0, i, editor.document.lineAt(i).text.length)})
}
}
editor.setDecorations(sliceDecoration, decorations)
break
}
case 'diff': {
const sliceContent = []
for(let i = 0; i < editor.document.lineCount; i++){
if(!sliceLines.has(i)){
sliceContent.push(editor.document.lineAt(i).text)
}
}
const sliceDoc = await vscode.workspace.openTextDocument({language: 'r', content: sliceContent.join('\n')})
void vscode.commands.executeCommand('vscode.diff', sliceDoc.uri, editor.document.uri)
break
}
}
}

function recreateSliceDecorationType() {
sliceDecoration?.dispose()
sliceDecoration = vscode.window.createTextEditorDecorationType({
opacity: getConfig().get<number>(Settings.StyleSliceOpacity)?.toString()
})
}

0 comments on commit b14038d

Please sign in to comment.