Repository: stateful/vscode-runme
Pull request #449 (2023-04-25 18:13:24)

Survey Windows users in CodeLens Run 
Solves #448 


Patch:
diff --git a/src/extension/executors/runner.ts b/src/extension/executors/runner.ts
index fdd95626..4cb530fc 100644
--- a/src/extension/executors/runner.ts
+++ b/src/extension/executors/runner.ts
@@ -113,6 +113,7 @@ export async function executeRunner(
     cwd,
     background,
     tty: interactive,
+    convertEol: (!mimeType || mimeType === 'text/plain'),
   })
 
   context.subscriptions.push(program)
diff --git a/src/extension/extension.ts b/src/extension/extension.ts
index 34809cd1..9a2760cd 100644
--- a/src/extension/extension.ts
+++ b/src/extension/extension.ts
@@ -67,11 +67,12 @@ export class RunmeExtension {
     const treeViewer = new RunmeLauncherProvider(getDefaultWorkspace())
     const uriHandler = new RunmeUriHandler(context)
     const winSurvey = new survey.WinDefaultShell(context)
+    const winCodeLensRunSurvey = new survey.SurveyWinCodeLensRun(context)
     const stopBackgroundTaskProvider = new StopBackgroundTaskProvider()
 
     const runCLI = runCLICommand(context.extensionUri, !!grpcRunner, server, kernel)
 
-    const codeLensProvider = new RunmeCodeLensProvider(serializer, runCLI, runner, kernel)
+    const codeLensProvider = new RunmeCodeLensProvider(serializer, runCLI, winCodeLensRunSurvey, runner, kernel)
 
     context.subscriptions.push(
       kernel,
@@ -79,6 +80,7 @@ export class RunmeExtension {
       server,
       treeViewer,
       winSurvey,
+      winCodeLensRunSurvey,
       workspace.registerNotebookSerializer(Kernel.type, serializer, {
         transientOutputs: true,
         transientCellMetadata: {
diff --git a/src/extension/provider/codelens.ts b/src/extension/provider/codelens.ts
index ed0c9a0d..4f475120 100644
--- a/src/extension/provider/codelens.ts
+++ b/src/extension/provider/codelens.ts
@@ -22,6 +22,7 @@ import { getAnnotations } from '../utils'
 import { Serializer } from '../../types'
 import { getCodeLensEnabled } from '../../utils/configuration'
 import { RunmeExtension } from '../extension'
+import type { SurveyWinCodeLensRun } from '../survey'
 
 import { RunmeTaskProvider } from './runmeTask'
 
@@ -53,6 +54,7 @@ export class RunmeCodeLensProvider implements CodeLensProvider, Disposable {
   constructor(
     protected serializer: SerializerBase,
     protected runCLI: ReturnType<typeof runCLICommand>,
+    protected surveyWinCodeLensRun: SurveyWinCodeLensRun,
     protected runner?: IRunner,
     protected kernel?: Kernel
   ) {
@@ -169,6 +171,11 @@ export class RunmeCodeLensProvider implements CodeLensProvider, Disposable {
       } break
 
       case 'run': {
+        if (this.surveyWinCodeLensRun.shouldPrompt()) {
+          await this.surveyWinCodeLensRun.prompt()
+          break
+        }
+
         const task = await RunmeTaskProvider.getRunmeTask(
           document.uri.fsPath,
           getAnnotations(cell.metadata).name,
diff --git a/src/extension/provider/runmeTask.ts b/src/extension/provider/runmeTask.ts
index 9b5e6853..c140c760 100644
--- a/src/extension/provider/runmeTask.ts
+++ b/src/extension/provider/runmeTask.ts
@@ -118,6 +118,7 @@ export class RunmeTaskProvider implements TaskProvider {
       cwd,
       environment,
       tty: interactive,
+      convertEol: true,
     }
 
     if (!environment) {
diff --git a/src/extension/runner.ts b/src/extension/runner.ts
index 37cd5044..b6d17075 100644
--- a/src/extension/runner.ts
+++ b/src/extension/runner.ts
@@ -439,7 +439,10 @@ export class GrpcRunnerProgramSession implements IRunnerProgramSession {
   } as const
 
   protected write(channel: 'stdout'|'stderr', bytes: Uint8Array): void {
-    if (this.convertEol && !this.isPseudoterminal()) {
+    if (
+      this.convertEol &&
+      !this.isPseudoterminal()
+    ) {
       const newBytes = new Array(bytes.byteLength)
 
       let i = 0, j = 0
diff --git a/src/extension/survey.ts b/src/extension/survey.ts
index ee9ead70..308df0ce 100644
--- a/src/extension/survey.ts
+++ b/src/extension/survey.ts
@@ -148,3 +148,53 @@ export class WinDefaultShell implements Disposable {
     this.#disposables.forEach(d => d.dispose())
   }
 }
+
+export class SurveyWinCodeLensRun implements Disposable {
+  static readonly #id: string = 'runme.surveyWinCodeLensRun'
+
+  constructor(
+    protected context: ExtensionContext
+  ) { }
+
+  shouldPrompt() {
+    return isWindows()
+  }
+
+  async prompt(): Promise<void> {
+    if (
+      this.context.globalState.get<boolean>(
+        SurveyWinCodeLensRun.#id,
+        false
+      ) ||
+      !this.shouldPrompt()
+    ) {
+      return
+    }
+
+    const option = await window.showInformationMessage(
+      // eslint-disable-next-line max-len
+      'Support for running scripts directly from markdown is currently not supported on Windows.\nPlease help us improve Runme on Windows: Click OK to share your interest in this feature.',
+      'OK',
+      'Don\'t ask again',
+      'Dismiss'
+    )
+
+    switch (option) {
+      case 'OK': {
+        TelemetryReporter.sendTelemetryEvent('survey.WinCodeLensRun', { })
+      } break
+
+      case 'Dismiss': {
+        return
+      }
+    }
+
+    await this.#done()
+  }
+
+  async #done() {
+    await this.context.globalState.update(SurveyWinCodeLensRun.#id, true)
+  }
+
+  dispose() { }
+}


Test patch:
diff --git a/tests/extension/runner.test.ts b/tests/extension/runner.test.ts
index 35507025..79e57dfd 100644
--- a/tests/extension/runner.test.ts
+++ b/tests/extension/runner.test.ts
@@ -8,15 +8,19 @@ import {
   GrpcRunner,
   GrpcRunnerEnvironment,
   GrpcRunnerProgramSession,
+  IRunner,
   RunProgramOptions
 } from '../../src/extension/runner'
 import type { ExecuteResponse } from '../../src/extension/grpc/runnerTypes'
 import { ActionCommand, RunmeCodeLensProvider } from '../../src/extension/provider/codelens'
 import { RunmeTaskProvider } from '../../src/extension/provider/runmeTask'
+import { isWindows } from '../../src/extension/utils'
+import { SurveyWinCodeLensRun } from '../../src/extension/survey'
 
 vi.mock('../../src/extension/utils', () => ({
   getGrpcHost: vi.fn().mockReturnValue('127.0.0.1:7863'),
-  getAnnotations: vi.fn().mockReturnValue({ })
+  getAnnotations: vi.fn().mockReturnValue({ }),
+  isWindows: vi.fn().mockReturnValue(false),
 }))
 
 vi.mock('vscode', async () => ({
@@ -542,8 +546,13 @@ suite('grpc Runner', () => {
 })
 
 suite('RunmeCodeLensProvider', () => {
+  beforeEach(() => {
+    vi.mocked(tasks.executeTask).mockClear()
+    vi.mocked(isWindows).mockClear()
+  })
+
   test('returns nothing without runner', async () => {
-    const provider = new RunmeCodeLensProvider({} as any, vi.fn())
+    const provider = createCodeLensProvider()
 
     const lenses = await provider.provideCodeLenses({} as any, {} as any)
 
@@ -565,11 +574,7 @@ suite('RunmeCodeLensProvider', () => {
       }))
     } as any
 
-    const provider = new RunmeCodeLensProvider(
-      serializer,
-      vi.fn(),
-      {} as any
-    )
+    const provider = createCodeLensProvider(serializer, {} as any)
 
     const lenses = await provider.provideCodeLenses(
       {
@@ -596,26 +601,35 @@ suite('RunmeCodeLensProvider', () => {
   })
 
   test('action callback for run command', async () => {
-    const provider = new RunmeCodeLensProvider(
-      {} as any,
-      vi.fn(),
-      {} as any
-    )
+    const provider = createCodeLensProvider({}, {} as any)
 
     await provider['codeLensActionCallback']({uri: { fsPath: '' }} as any, {} as any, {} as any, 0, 'run')
 
     expect(RunmeTaskProvider.getRunmeTask).toBeCalledTimes(1)
 
+    expect(isWindows).toBeCalledTimes(1)
+
     expect(tasks.executeTask).toBeCalledTimes(1)
     expect(tasks.executeTask).toBeCalledWith({})
   })
 
+  test('action callback for run command on windows', async () => {
+    const provider = createCodeLensProvider({}, {} as any)
+
+    provider['surveyWinCodeLensRun']['prompt'] = vi.fn()
+
+    vi.mocked(isWindows).mockReturnValueOnce(true)
+
+    await provider['codeLensActionCallback']({uri: { fsPath: '' }} as any, {} as any, {} as any, 0, 'run')
+
+    expect(isWindows).toBeCalledTimes(1)
+    expect(provider['surveyWinCodeLensRun']['prompt']).toBeCalledTimes(1)
+
+    expect(tasks.executeTask).toBeCalledTimes(0)
+  })
+
   test('action callback for open command', async () => {
-    const provider = new RunmeCodeLensProvider(
-      {} as any,
-      vi.fn(),
-      {} as any
-    )
+    const provider = createCodeLensProvider()
 
     const document = { uri: { fsPath: '' } }
     await provider['codeLensActionCallback'](document as any, {} as any, {} as any, 0, 'open')
@@ -671,3 +685,15 @@ async function createNewSession(options: Partial<RunProgramOptions> = {}, runner
     server,
   }
 }
+
+function createCodeLensProvider(
+  serializer = {} as any,
+  runner?: IRunner
+) {
+  return new RunmeCodeLensProvider(
+    serializer,
+    vi.fn(),
+    new SurveyWinCodeLensRun({} as any),
+    runner
+  )
+}