Skip to content

Commit

Permalink
feat: workspace symbols
Browse files Browse the repository at this point in the history
  • Loading branch information
Desdaemon committed Aug 25, 2023
1 parent 1241b18 commit 76bfeab
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 101 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Works for `record`s, `template`s and `env.ref()`!

[![model demo](https://raw.githubusercontent.com/Desdaemon/odoo-lsp/main/static/model.gif)](https://asciinema.org/a/604545)

### Browse models and XML records as workspace symbols

[![symbols demo](https://raw.githubusercontent.com/Desdaemon/odoo-lsp/main/static/symbols.gif)](https://asciinema.org/a/604560)

For more features check out the [wiki].

## Install
Expand Down
200 changes: 101 additions & 99 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,31 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import { dirname } from "node:path";
import {
// languages,
workspace,
window,
commands,
EventEmitter,
// commands,
// EventEmitter,
ExtensionContext,
// InlayHintsProvider,
// TextDocument,
// CancellationToken,
Range,
// Range,
// InlayHint,
TextDocumentChangeEvent,
// TextDocumentChangeEvent,
// ProviderResult,
// WorkspaceEdit,
// TextEdit,
Selection,
Uri,
// Selection,
// Uri,
} from "vscode";

import {
// CloseAction,
// CloseHandlerResult,
Disposable,
// Disposable,
// ErrorAction,
// ErrorHandlerResult,
Executable,
Expand All @@ -39,28 +40,29 @@ import {
let client: LanguageClient;
// type a = Parameters<>;

export async function activate(context: ExtensionContext) {
let disposable = commands.registerCommand("helloworld.helloWorld", async (uri) => {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
const url = Uri.parse("/home/victor/Documents/test-dir/nrs/another.nrs");
let document = await workspace.openTextDocument(uri);
await window.showTextDocument(document);
export async function activate(_context: ExtensionContext) {
// let disposable = commands.registerCommand("helloworld.helloWorld", async (uri) => {
// // The code you place here will be executed every time your command is executed
// // Display a message box to the user
// const url = Uri.parse("/home/victor/Documents/test-dir/nrs/another.nrs");
// let document = await workspace.openTextDocument(uri);
// await window.showTextDocument(document);

// console.log(uri)
window.activeTextEditor.document;
let editor = window.activeTextEditor;
let range = new Range(1, 1, 1, 1);
editor.selection = new Selection(range.start, range.end);
});
// // console.log(uri)
// window.activeTextEditor.document;
// let editor = window.activeTextEditor;
// let range = new Range(1, 1, 1, 1);
// editor.selection = new Selection(range.start, range.end);
// });

context.subscriptions.push(disposable);
// context.subscriptions.push(disposable);

const traceOutputChannel = window.createOutputChannel("Odoo LSP");
const command = process.env.SERVER_PATH || "odoo-lsp";
const run: Executable = {
command,
options: {
cwd: workspace.workspaceFolders.length ? dirname(workspace.workspaceFolders[0].uri.fsPath) : void 0,
env: {
...process.env,
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -84,7 +86,7 @@ export async function activate(context: ExtensionContext) {
],
synchronize: {
// Notify the server about file changes to '.clientrc files contained in the workspace
fileEvents: workspace.createFileSystemWatcher("**/.clientrc"),
fileEvents: workspace.createFileSystemWatcher("**/.odoo_lsp*"),
},
traceOutputChannel,
};
Expand All @@ -102,80 +104,80 @@ export function deactivate(): Thenable<void> | undefined {
return client.stop();
}

export function activateInlayHints(ctx: ExtensionContext) {
const maybeUpdater = {
hintsProvider: null as Disposable | null,
updateHintsEventEmitter: new EventEmitter<void>(),

async onConfigChange() {
this.dispose();

const event = this.updateHintsEventEmitter.event;
// this.hintsProvider = languages.registerInlayHintsProvider(
// { scheme: "file", language: "nrs" },
// // new (class implements InlayHintsProvider {
// // onDidChangeInlayHints = event;
// // resolveInlayHint(hint: InlayHint, token: CancellationToken): ProviderResult<InlayHint> {
// // const ret = {
// // label: hint.label,
// // ...hint,
// // };
// // return ret;
// // }
// // async provideInlayHints(
// // document: TextDocument,
// // range: Range,
// // token: CancellationToken
// // ): Promise<InlayHint[]> {
// // const hints = (await client
// // .sendRequest("custom/inlay_hint", { path: document.uri.toString() })
// // .catch(err => null)) as [number, number, string][];
// // if (hints == null) {
// // return [];
// // } else {
// // return hints.map(item => {
// // const [start, end, label] = item;
// // let startPosition = document.positionAt(start);
// // let endPosition = document.positionAt(end);
// // return {
// // position: endPosition,
// // paddingLeft: true,
// // label: [
// // {
// // value: `${label}`,
// // // location: {
// // // uri: document.uri,
// // // range: new Range(1, 0, 1, 0)
// // // }
// // command: {
// // title: "hello world",
// // command: "helloworld.helloWorld",
// // arguments: [document.uri],
// // },
// // },
// // ],
// // };
// // });
// // }
// // }
// // })()
// );
},

onDidChangeTextDocument({ contentChanges, document }: TextDocumentChangeEvent) {
// debugger
// this.updateHintsEventEmitter.fire();
},

dispose() {
this.hintsProvider?.dispose();
this.hintsProvider = null;
this.updateHintsEventEmitter.dispose();
},
};

workspace.onDidChangeConfiguration(maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions);
workspace.onDidChangeTextDocument(maybeUpdater.onDidChangeTextDocument, maybeUpdater, ctx.subscriptions);

maybeUpdater.onConfigChange().catch(console.error);
}
// export function activateInlayHints(ctx: ExtensionContext) {
// const maybeUpdater = {
// hintsProvider: null as Disposable | null,
// updateHintsEventEmitter: new EventEmitter<void>(),

// async onConfigChange() {
// this.dispose();

// const event = this.updateHintsEventEmitter.event;
// // this.hintsProvider = languages.registerInlayHintsProvider(
// // { scheme: "file", language: "nrs" },
// // // new (class implements InlayHintsProvider {
// // // onDidChangeInlayHints = event;
// // // resolveInlayHint(hint: InlayHint, token: CancellationToken): ProviderResult<InlayHint> {
// // // const ret = {
// // // label: hint.label,
// // // ...hint,
// // // };
// // // return ret;
// // // }
// // // async provideInlayHints(
// // // document: TextDocument,
// // // range: Range,
// // // token: CancellationToken
// // // ): Promise<InlayHint[]> {
// // // const hints = (await client
// // // .sendRequest("custom/inlay_hint", { path: document.uri.toString() })
// // // .catch(err => null)) as [number, number, string][];
// // // if (hints == null) {
// // // return [];
// // // } else {
// // // return hints.map(item => {
// // // const [start, end, label] = item;
// // // let startPosition = document.positionAt(start);
// // // let endPosition = document.positionAt(end);
// // // return {
// // // position: endPosition,
// // // paddingLeft: true,
// // // label: [
// // // {
// // // value: `${label}`,
// // // // location: {
// // // // uri: document.uri,
// // // // range: new Range(1, 0, 1, 0)
// // // // }
// // // command: {
// // // title: "hello world",
// // // command: "helloworld.helloWorld",
// // // arguments: [document.uri],
// // // },
// // // },
// // // ],
// // // };
// // // });
// // // }
// // // }
// // // })()
// // );
// },

// onDidChangeTextDocument({ contentChanges, document }: TextDocumentChangeEvent) {
// // debugger
// // this.updateHintsEventEmitter.fire();
// },

// dispose() {
// this.hintsProvider?.dispose();
// this.hintsProvider = null;
// this.updateHintsEventEmitter.dispose();
// },
// };

// workspace.onDidChangeConfiguration(maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions);
// workspace.onDidChangeTextDocument(maybeUpdater.onDidChangeTextDocument, maybeUpdater, ctx.subscriptions);

// maybeUpdater.onConfigChange().catch(console.error);
// }
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
"default": "off",
"description": "Traces the communication between VS Code and the language server."
},
"odoo-lsp.symbols.limit": {
"type": "number",
"default": 80,
"description": "Maximum amount of workspace symbols to view at once."
},
"odoo-lsp.module.roots": {
"type": "array",
"scope": "resource",
Expand Down
6 changes: 6 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ use serde::Deserialize;
#[derive(Deserialize, Debug, Clone)]
pub struct Config {
pub module: Option<ModuleConfig>,
pub symbols: Option<SymbolsConfig>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct ModuleConfig {
pub roots: Option<Vec<String>>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct SymbolsConfig {
pub limit: Option<u32>,
}
39 changes: 37 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::atomic::{AtomicBool, AtomicUsize};

use catch_panic::CatchPanic;
use dashmap::{DashMap, DashSet};
Expand All @@ -17,7 +17,7 @@ use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer, LspService, Server};
use tree_sitter::{Parser, Tree};

use odoo_lsp::config::{Config, ModuleConfig};
use odoo_lsp::config::{Config, ModuleConfig, SymbolsConfig};
use odoo_lsp::index::ModuleIndex;
use odoo_lsp::model::ModelLocation;
use odoo_lsp::{format_loc, utils::*};
Expand All @@ -35,6 +35,7 @@ pub struct Backend {
roots: DashSet<String>,
capabilities: Capabilities,
root_setup: AtomicBool,
symbols_limit: AtomicUsize,
}

#[derive(Debug, Default)]
Expand Down Expand Up @@ -98,6 +99,7 @@ impl LanguageServer for Backend {
capabilities: ServerCapabilities {
definition_provider: Some(OneOf::Left(true)),
references_provider: Some(OneOf::Left(true)),
workspace_symbol_provider: Some(OneOf::Left(true)),
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::INCREMENTAL)),
completion_provider: Some(CompletionOptions {
resolve_provider: None,
Expand Down Expand Up @@ -131,6 +133,7 @@ impl LanguageServer for Backend {
roots: _,
capabilities: _,
root_setup: _,
symbols_limit: _,
} = self;
document_map.remove(path);
record_ranges.remove(path);
Expand Down Expand Up @@ -454,6 +457,34 @@ impl LanguageServer for Backend {
async fn execute_command(&self, _: ExecuteCommandParams) -> Result<Option<Value>> {
Ok(None)
}
#[allow(deprecated)]
async fn symbol(&self, params: WorkspaceSymbolParams) -> Result<Option<Vec<SymbolInformation>>> {
let query = &params.query;
let records = self.module_index.records.iter().filter_map(|entry| {
entry.id.contains(query).then(|| SymbolInformation {
name: entry.id.to_string(),
kind: SymbolKind::VARIABLE,
tags: None,
deprecated: None,
location: entry.location.clone(),
container_name: None,
})
});
let models = self.module_index.models.iter().filter_map(|entry| {
entry.0.as_ref().and_then(|loc| {
entry.key().contains(query).then(|| SymbolInformation {
name: entry.key().clone(),
kind: SymbolKind::CONSTANT,
tags: None,
deprecated: None,
location: loc.0.clone(),
container_name: None,
})
})
});
let limit = self.symbols_limit.load(Relaxed);
Ok(Some(models.chain(records).take(limit).collect()))
}
}

struct TextDocumentItem {
Expand Down Expand Up @@ -667,6 +698,9 @@ impl Backend {
Ok(Some(locations))
}
async fn on_change_config(&self, config: Config) {
if let Some(SymbolsConfig { limit: Some(limit) }) = config.symbols {
self.symbols_limit.store(limit as usize, Relaxed);
}
let Some(ModuleConfig { roots: Some(roots), .. }) = config.module else {
return;
};
Expand Down Expand Up @@ -710,6 +744,7 @@ async fn main() {
capabilities: Default::default(),
root_setup: Default::default(),
ast_map: DashMap::new(),
symbols_limit: AtomicUsize::new(80),
})
.finish();

Expand Down
Binary file added static/symbols.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 76bfeab

Please sign in to comment.