Skip to content

Commit

Permalink
Merge pull request #904 from BetterThanTomorrow/pez/prompt-tweaks
Browse files Browse the repository at this point in the history
Load current repl window namespace
  • Loading branch information
PEZ authored Jan 1, 2021
2 parents 778cfa1 + bfbbc7f commit bcf89c4
Show file tree
Hide file tree
Showing 15 changed files with 140 additions and 78 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changes to Calva.

## [Unreleased]
- [Reactivate definitions/navigation in core and library files](https://github.com/BetterThanTomorrow/calva/issues/915)
- [Make load-file available in the output window](https://github.com/BetterThanTomorrow/calva/issues/910)
- [Make the ns in the repl prompt a peekable symbol](https://github.com/BetterThanTomorrow/calva/issues/904)

## [2.0.142 and 2.0.143 - 2020-12-30]
- No changes besides version number. Released due to vsix publishing issues.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 36 additions & 2 deletions docs/site/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ In ClojureScript projects the window will be associated with the `cljs` REPL onc

The first prompt is from when the `clj` REPL is connected, the second when Calva has a `cljs` REPL connection. The first part of the prompt tells you which REPL type the window is currently connected to. This gets important when the file/window is used as an interactive REPL.

## Find the Output/REPL Window

If you quickly want to open and switch to the output window there is the command **Calva: Show Output Window**, `ctrl+alt+c o`.

## Evaluating code

The window will be automatically associated with the REPL and the namespace of any project Clojure/ClojureScript file you evaluate code in. So for instance if you evaluate this code in a `clj` file with the namespace `fresh-reagent.handler`:
Expand Down Expand Up @@ -75,9 +79,39 @@ For printed stacktraces, when source locations are available (Clojure files) you

![Stack trace clicking and peeking definition](images/howto/output/stack-traces.gif "Stack trace clicking and peeking definition")

## Find the Output/REPL Window
## Load Current Namespace

If you quickly want to open and switch to the output window there is the command **Calva: Show Output Window**, `ctrl+alt+c o`.
When navigating namespaces it is easy to [forget to first require them](https://clojure.org/guides/repl/navigating_namespaces#_how_things_can_go_wrong) and that can be a bit tricky to fix. To help with this Calva's command **Load Current File** also works in the output window, but then acts like **Load Current Namespace**.

Consider you have two files, `pez/xxx.clj` and `pez/yyy.clj`, where `pez.yyy` requires in `pez.yyy`.

```clojure
(ns pez.xxx)

(def a :xxx-a)

(def b :xxx-b)
```

```clojure
(ns pez.yyy
(:require [pez.xxx]))

(def a :yyy-a)

(println "Hello" pez.xxx/a)
```

Then with a freshly jacked-in REPL you do `(ns pez.yyy)` and want to work with the vars defined there. Clojure will complain. But if you **Load Current File**, it will start working. Something like so:

![Load Current Namespace in the Calva Output Window](images/howto/output/load-current-namespace.png)

!!! Note
This currently suffers from a limitation in Calva where it won't reload dependencies, so you will sometimes have to do this ”manually” anyway (by opening the files and loading them). See [Calva issue #907](https://github.com/BetterThanTomorrow/calva/issues/907)

### Peek Current Namespace

A somewhat hidden feature: You can see documentation for, peek and navigate to a namespace by hovering on the namespace symbol in one of the repl window prompts (just like you would if it was not in the prompt 😄).

## Paredit Enabled

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,11 @@
"command": "calva.refactor.extractFunction",
"title": "Extract to New Function",
"category": "Calva Refactor"
},
{
"command": "calva.diagnostics.serverInfo",
"title": "LSP Server Info",
"category": "Calva Diagnostics"
}
],
"keybindings": [
Expand Down
2 changes: 1 addition & 1 deletion src/calva-fmt/atom-language-clojure/grammars/clojure.cson
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@
'prompt':
'patterns': [
{
'match': '^([\\p{L}0-9]+)(::)([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*0-9]+)(=>)([ ])'
'match': '^([\\p{L}0-9]+)()([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*0-9]+)(>)([ ])'
'captures':
'1':
'name': 'keyword.control.prompt.clojure'
Expand Down
16 changes: 8 additions & 8 deletions src/calva-fmt/atom-language-clojure/spec/clojure-spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -333,18 +333,18 @@ describe "Clojure grammar", ->

describe "replPrompt", ->
it "tokenizes repl prompt", ->
{tokens} = grammar.tokenizeLine "foo::bar.baz-2=> "
{tokens} = grammar.tokenizeLine "foobar.baz-2꞉> "
expect(tokens[0]).toEqual value: "foo", scopes: ["source.clojure", "keyword.control.prompt.clojure"]
expect(tokens[1]).toEqual value: "::", scopes: ["source.clojure", "keyword.control.prompt.clojure"]
expect(tokens[1]).toEqual value: "", scopes: ["source.clojure", "keyword.control.prompt.clojure"]
expect(tokens[2]).toEqual value: "bar.baz-2", scopes: ["source.clojure", "meta.symbol.namespace.prompt.clojure"]
expect(tokens[3]).toEqual value: "=>", scopes: ["source.clojure", "keyword.control.prompt.clojure"]
expect(tokens[3]).toEqual value: ">", scopes: ["source.clojure", "keyword.control.prompt.clojure"]
it "does not tokenize repl prompt when prepended with anything", ->
{tokens} = grammar.tokenizeLine " foo::bar.baz-2=> "
{tokens} = grammar.tokenizeLine " foobar.baz-2꞉> "
expect(tokens[0]).toEqual value: " ", scopes: ["source.clojure"]
expect(tokens[1]).toEqual value: "foo::bar.baz-2=>", scopes: ["source.clojure", "meta.symbol.clojure"]
it "does not tokenize repl prompt when not followed by space", ->
{tokens} = grammar.tokenizeLine "foo::bar.baz-2=>"
expect(tokens[0]).toEqual value: "foo::bar.baz-2=>", scopes: ["source.clojure", "meta.symbol.clojure"]
expect(tokens[1]).toEqual value: "foo", scopes: ["source.clojure", "meta.symbol.clojure"]
it "does not tokenize repl prompt when not followed by hard space", ->
{tokens} = grammar.tokenizeLine "foobar.baz-2>"
expect(tokens[0]).toEqual value: "foo", scopes: ["source.clojure", "meta.symbol.clojure"]

describe "firstLineMatch", ->
it "recognises interpreter directives", ->
Expand Down
2 changes: 1 addition & 1 deletion src/calva-fmt/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as config from './config'

function getLanguageConfiguration(autoIndentOn: boolean): vscode.LanguageConfiguration {
return {
wordPattern: /[^\s,#()[\]{};"\\\@\']+/,
wordPattern: /[^\s,#()[\]{};"\\\@\']+/, // NB: The ꞉ there is not a regular colon
onEnterRules: autoIndentOn ? [
// This is madness, but the only way to stop vscode from indenting new lines
{
Expand Down
4 changes: 2 additions & 2 deletions src/cursor-doc/clojure-lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ toplevel.terminal("ws-nl", /(\r|\n|\r\n)/, (l, m) => ({ type: "ws" }))
toplevel.terminal("ws-other", /[\f\u000B\u001C\u001D\u001E\u001F\u2028\u2029\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2008\u2009\u200a\u205f\u3000]+/, (l, m) => ({ type: "ws" }))
// comments
toplevel.terminal("comment", /;.*/, (l, m) => ({ type: "comment" }))
// Calva repl prompt
toplevel.terminal("comment", /^[^()[\]\{\},~@`^\"\s;]+::[^()[\]\{\},~@`^\"\s;]+=> /, (l, m) => ({ type: "prompt" }))
// Calva repl prompt, it contains special colon symbols and a hard space
toplevel.terminal("comment", /^[^()[\]\{\},~@`^\"\s;]+[^()[\]\{\},~@`^\"\s;]+> /, (l, m) => ({ type: "prompt" }))

// current idea for prefixing data reader
// (#[^\(\)\[\]\{\}"_@~\s,]+[\s,]*)*
Expand Down
17 changes: 11 additions & 6 deletions src/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { DEBUG_ANALYTICS } from './debugger/calva-debug';
import * as namespace from './namespace';
import * as replHistory from './results-output/repl-history';
import { formatAsLineComments } from './results-output/util';
import * as fs from 'fs';

function interruptAllEvaluations() {
if (util.getConnectedState()) {
Expand Down Expand Up @@ -217,22 +218,26 @@ function evaluateCurrentForm(document = {}, options = {}) {
async function loadFile(document, pprintOptions: PrettyPrintingOptions) {
const current = state.deref();
const doc = util.getDocument(document);
const fileName = util.getFileName(doc);
const fileType = util.getFileType(doc);
const ns = namespace.getNamespace(doc);
const session = namespace.getSession(util.getFileType(doc));
const shortFileName = path.basename(fileName);
const dirName = path.dirname(fileName);

if (doc && !outputWindow.isResultsDoc(doc) && doc.languageId == "clojure" && fileType != "edn" && current.get('connected')) {
if (doc && doc.languageId == "clojure" && fileType != "edn" && current.get('connected')) {
state.analytics().logEvent("Evaluation", "LoadFile").send();
const info = await session.info(ns, ns);
const fileName = outputWindow.isResultsDoc(doc) ? info.file : util.getFileName(doc);
const shortFileName = path.basename(fileName);
const dirName = path.dirname(fileName);
const filePath = outputWindow.isResultsDoc(doc) ? vscode.Uri.parse(fileName).path : doc.fileName;
const fileContents = await util.getFileContents(filePath);

outputWindow.append("; Evaluating file: " + fileName);

await session.eval("(in-ns '" + ns + ")", session.client.ns).value;

const res = session.loadFile(doc.getText(), {
const res = session.loadFile(fileContents, {
fileName: fileName,
filePath: doc.fileName,
filePath,
stdout: m => outputWindow.append(normalizeNewLines(m.indexOf(dirName) < 0 ? m.replace(shortFileName, fileName) : m)),
stderr: m => outputWindow.append('; ' + normalizeNewLines(m.indexOf(dirName) < 0 ? m.replace(shortFileName, fileName) : m, true)),
pprintOptions: pprintOptions
Expand Down
16 changes: 8 additions & 8 deletions src/extension-test/unit/cursor-doc/clojure-lexer-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,31 +376,31 @@ describe('Scanner', () => {
});
});
it('tokenizes the Calva repl prompt', () => {
const tokens = scanner.processLine('foo::bar.baz=> ()');
const tokens = scanner.processLine('foobar.baz꞉> ()');
expect(tokens[0].type).toBe('prompt');
expect(tokens[0].raw).toBe('foo::bar.baz=> ');
expect(tokens[0].raw).toBe('foobar.baz꞉> ');
expect(tokens[1].type).toBe('open');
expect(tokens[1].raw).toBe('(');
expect(tokens[2].type).toBe('close');
expect(tokens[2].raw).toBe(')');
});
it('only tokenizes the Calva repl prompt if it is at the start of a line', () => {
const tokens = scanner.processLine(' foo::bar.baz=> ()');
const tokens = scanner.processLine(' foobar.baz꞉> ()');
expect(tokens[0].type).toBe('ws');
expect(tokens[0].raw).toBe(' ');
expect(tokens[1].type).toBe('id');
expect(tokens[1].raw).toBe('foo::bar.baz=>');
expect(tokens[2].type).toBe('ws');
expect(tokens[2].raw).toBe(' ');
expect(tokens[1].raw).toBe('foobar.baz>');
expect(tokens[2].type).toBe('junk');
expect(tokens[2].raw).toBe(' ');
expect(tokens[3].type).toBe('open');
expect(tokens[3].raw).toBe('(');
expect(tokens[4].type).toBe('close');
expect(tokens[4].raw).toBe(')');
});
it('only tokenizes the Calva repl prompt if it ends with a space', () => {
const tokens = scanner.processLine('foo::bar.baz=>()');
const tokens = scanner.processLine('foobar.baz>()');
expect(tokens[0].type).toBe('id');
expect(tokens[0].raw).toBe('foo::bar.baz=>');
expect(tokens[0].raw).toBe('foobar.baz>');
expect(tokens[1].type).toBe('open');
expect(tokens[1].raw).toBe('(');
expect(tokens[2].type).toBe('close');
Expand Down
4 changes: 2 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as util from './utilities'
import status from './status';
import connector from './connector';
import CalvaCompletionItemProvider from './providers/completion';
import TextDocumentContentProvider from './providers/content';
import JarContentProvider from './providers/content';
import HoverProvider from './providers/hover';
import * as definition from './providers/definition';
import { CalvaSignatureHelpProvider } from './providers/signature';
Expand Down Expand Up @@ -198,7 +198,7 @@ function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.languages.registerSignatureHelpProvider(state.documentSelector, new CalvaSignatureHelpProvider(), ' ', ' '));


vscode.workspace.registerTextDocumentContentProvider('jar', new TextDocumentContentProvider());
vscode.workspace.registerTextDocumentContentProvider('jar', new JarContentProvider());

// //EVENTS
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument((document) => {
Expand Down
35 changes: 21 additions & 14 deletions src/lsp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ function createClient(jarPath: string): LanguageClient {

type ClojureLspCommand = {
command: string,
extraParamFn?: () => Thenable<string>;
extraParamFn?: () => Thenable<string>,
category?: string;
}

function makePromptForInput(placeHolder: string) {
Expand Down Expand Up @@ -150,11 +151,16 @@ const clojureLspCommands: ClojureLspCommand[] = [
{
command: 'extract-function',
extraParamFn: makePromptForInput('Function name')
},
{
command: 'server-info',
category: 'calva.diagnostics'
}
]

function registerLspCommand(client: LanguageClient, command: ClojureLspCommand): vscode.Disposable {
const vscodeCommand = `calva.refactor.${command.command.replace(/-[a-z]/g, (m) => m.substring(1).toUpperCase())}`;
const category = command.category ? command.category : 'calva.refactor';
const vscodeCommand = `${category}.${command.command.replace(/-[a-z]/g, (m) => m.substring(1).toUpperCase())}`;
return vscode.commands.registerCommand(vscodeCommand, async () => {
const editor = vscode.window.activeTextEditor;
const document = util.getDocument(editor.document);
Expand Down Expand Up @@ -193,18 +199,19 @@ function activate(context: vscode.ExtensionContext): LanguageClient {
})
context.subscriptions.push(client.start());

const jarEventEmitter: vscode.EventEmitter<vscode.Uri> = new vscode.EventEmitter();
const contentsRequest = new RequestType<string, string, string, vscode.CancellationToken>('clojure/dependencyContents');
const textDocumentContentProvider: vscode.TextDocumentContentProvider = {
onDidChange: jarEventEmitter.event,
provideTextDocumentContent: async (uri: vscode.Uri, token: vscode.CancellationToken): Promise<string> => {
const v = await client.sendRequest<any, string, string, vscode.CancellationToken>(contentsRequest,
{ uri: decodeURIComponent(uri.toString()) },
token);
return v || '';
}
};
context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider('jar', textDocumentContentProvider));
// TODO: We don't need two jar content providers, when would this one be needed?
//const jarEventEmitter: vscode.EventEmitter<vscode.Uri> = new vscode.EventEmitter();
//const contentsRequest = new RequestType<string, string, string, vscode.CancellationToken>('clojure/dependencyContents');
//const textDocumentContentProvider: vscode.TextDocumentContentProvider = {
// onDidChange: jarEventEmitter.event,
// provideTextDocumentContent: async (uri: vscode.Uri, token: vscode.CancellationToken): Promise<string> => {
// const v = await client.sendRequest<any, string, string, vscode.CancellationToken>(contentsRequest,
// { uri: decodeURIComponent(uri.toString()) },
// token);
// return v || '';
// }
//};
//context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider('jar', textDocumentContentProvider));

// The title of this command is dictated by clojure-lsp and is executed when the user clicks the references code lens above a symbol
context.subscriptions.push(vscode.commands.registerCommand('code-lens-references', async (_, line, character) => {
Expand Down
31 changes: 3 additions & 28 deletions src/providers/content.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,15 @@
import * as vscode from 'vscode';
import * as state from '../state';
import * as os from 'os';
import * as fs from 'fs';
import * as JSZip from 'jszip';
import * as util from '../utilities'

export default class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
export default class JarContentProvider implements vscode.TextDocumentContentProvider {
state: any;

constructor() {
this.state = state;
}

provideTextDocumentContent(uri, token) {
let current = this.state.deref();
if (current.get('connected')) {
return new Promise<string>((resolve, reject) => {
let rawPath = uri.path,
pathToFileInJar = rawPath.slice(rawPath.search('!/') + 2),
pathToJar = rawPath.slice('file:'.length);

pathToJar = pathToJar.slice(0, pathToJar.search('!'));
if (os.platform() === 'win32') {
pathToJar = pathToJar.replace(/\//g, '\\').slice(1);
}

fs.readFile(pathToJar, (err, data) => {
let zip = new JSZip();
zip.loadAsync(data).then((new_zip) => {
new_zip.file(pathToFileInJar).async("string").then((value) => {
resolve(value);
})
})
});
});
} else {
console.warn("Unable to provide textdocumentcontent, not connected to nREPL");
}
return util.getJarContents(uri);
}
};
2 changes: 1 addition & 1 deletion src/results-output/results-doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ let showPrompt: { [id: string]: boolean } = {
};

export function getPrompt(): string {
let prompt = `${_sessionType}::${getNs()}=> `;
let prompt = `${_sessionType}${getNs()}꞉> `;
if (showPrompt[_sessionType]) {
showPrompt[_sessionType] = false;
prompt = `${prompt} ${PROMPT_HINT}`
Expand Down
Loading

0 comments on commit bcf89c4

Please sign in to comment.