mirror of
https://github.com/dalance/svls.git
synced 2025-07-07 20:15:00 +00:00
Initial commit
This commit is contained in:
commit
a05dd7e3f3
14 changed files with 2107 additions and 0 deletions
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: dalance
|
25
.github/workflows/periodic.yml
vendored
Normal file
25
.github/workflows/periodic.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
name: Periodic
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 0 * * SUN
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
rust: [stable, beta, nightly]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Setup Rust
|
||||
uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: ${{ matrix.rust }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Run tests
|
||||
run: cargo test
|
23
.github/workflows/regression.yml
vendored
Normal file
23
.github/workflows/regression.yml
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
name: Regression
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
rust: [stable]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Setup Rust
|
||||
uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: ${{ matrix.rust }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Run tests
|
||||
run: cargo test
|
45
.github/workflows/release.yml
vendored
Normal file
45
.github/workflows/release.yml
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
rust: [stable]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Setup Rust
|
||||
uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: ${{ matrix.rust }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Setup MUSL
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
sudo apt-get -qq install musl-tools
|
||||
- name: Build for linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: make release_lnx
|
||||
- name: Build for macOS
|
||||
if: matrix.os == 'macOS-latest'
|
||||
run: make release_mac
|
||||
- name: Build for Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: make release_win
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body: '[Changelog](https://github.com/dalance/svls/blob/master/CHANGELOG.md)'
|
||||
files: '*.zip'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
**/*.rs.bk
|
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Change Log
|
||||
|
||||
## [Unreleased](https://github.com/dalance/svlint/compare/v0.1.0...Unreleased) - ReleaseDate
|
1580
Cargo.lock
generated
Normal file
1580
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
38
Cargo.toml
Normal file
38
Cargo.toml
Normal file
|
@ -0,0 +1,38 @@
|
|||
[package]
|
||||
name = "svls"
|
||||
version = "0.1.0"
|
||||
authors = ["dalance@gmail.com"]
|
||||
repository = "https://github.com/dalance/svls"
|
||||
keywords = ["lsp", "language-server", "verilog", "systemverilog"]
|
||||
categories = ["development-tools"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
description = "SystemVerilog language server"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata.release]
|
||||
dev-version-ext = "pre"
|
||||
pre-release-commit-message = "Prepare to v{{version}}"
|
||||
pro-release-commit-message = "Start next development iteration v{{version}}"
|
||||
tag-message = "Bump version to {{version}}"
|
||||
tag-prefix = ""
|
||||
pre-release-replacements = [
|
||||
{file="CHANGELOG.md", search="Unreleased", replace="v{{version}}"},
|
||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}"},
|
||||
{file="CHANGELOG.md", search="Change Log", replace="Change Log\n\n## [Unreleased](https://github.com/dalance/svls/compare/v{{version}}...Unreleased) - ReleaseDate"},
|
||||
{file="snapcraft.yaml", search="version v[0-9\\.]+", replace="version v{{version}}"},
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
futures = "0.1"
|
||||
jsonrpc-core = "13.2"
|
||||
log = "0.4"
|
||||
serde_json = "1.0"
|
||||
simplelog = "0.7"
|
||||
structopt = "0.3"
|
||||
svlint = "0.2.14"
|
||||
sv-parser = "0.4.3"
|
||||
tokio = "0.1"
|
||||
toml = "0.4"
|
||||
tower-lsp = "0.4"
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
33
Makefile
Normal file
33
Makefile
Normal file
|
@ -0,0 +1,33 @@
|
|||
VERSION = $(patsubst "%",%, $(word 3, $(shell grep version Cargo.toml)))
|
||||
BUILD_TIME = $(shell date +"%Y/%m/%d %H:%M:%S")
|
||||
GIT_REVISION = $(shell git log -1 --format="%h")
|
||||
RUST_VERSION = $(word 2, $(shell rustc -V))
|
||||
LONG_VERSION = "$(VERSION) ( rev: $(GIT_REVISION), rustc: $(RUST_VERSION), build at: $(BUILD_TIME) )"
|
||||
BIN_NAME = svls
|
||||
|
||||
export LONG_VERSION
|
||||
|
||||
.PHONY: all test clean release_lnx release_win release_mac
|
||||
|
||||
all: test
|
||||
|
||||
test:
|
||||
cargo test
|
||||
|
||||
watch:
|
||||
cargo watch test
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
release_lnx:
|
||||
cargo build --release --target=x86_64-unknown-linux-musl
|
||||
zip -j ${BIN_NAME}-v${VERSION}-x86_64-lnx.zip target/x86_64-unknown-linux-musl/release/${BIN_NAME}
|
||||
|
||||
release_win:
|
||||
cargo build --release --target=x86_64-pc-windows-msvc
|
||||
7z a ${BIN_NAME}-v${VERSION}-x86_64-win.zip target/x86_64-pc-windows-msvc/release/${BIN_NAME}.exe
|
||||
|
||||
release_mac:
|
||||
cargo build --release --target=x86_64-apple-darwin
|
||||
zip -j ${BIN_NAME}-v${VERSION}-x86_64-mac.zip target/x86_64-apple-darwin/release/${BIN_NAME}
|
46
README.md
Normal file
46
README.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# svls
|
||||
|
||||
SystemVerilog language server
|
||||
|
||||
[](https://github.com/dalance/svls/actions)
|
||||
[](https://build.snapcraft.io/user/dalance/svls)
|
||||
|
||||
[](https://crates.io/crates/svls)
|
||||
[](https://snapcraft.io/svls)
|
||||
|
||||
## Feature
|
||||
|
||||
* Diagnostic with [svlint](https://github.com/dalance/svlint).
|
||||
|
||||
## Installation
|
||||
|
||||
### Download binary
|
||||
|
||||
Download from [release page](https://github.com/dalance/svls/releases/latest), and extract to the directory in PATH.
|
||||
|
||||
### snapcraft
|
||||
|
||||
You can install from [snapcraft](https://snapcraft.io/svls)
|
||||
|
||||
```
|
||||
sudo snap install svls
|
||||
```
|
||||
|
||||
### Cargo
|
||||
|
||||
You can install by [cargo](https://crates.io/crates/svls).
|
||||
|
||||
```
|
||||
cargo install svls
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Neovim with [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim)
|
||||
|
||||
```
|
||||
let g:LanguageClient_serverCommands = {
|
||||
\ 'systemverilog': ['svls'],
|
||||
\ }
|
||||
|
||||
```
|
31
snapcraft.yaml
Normal file
31
snapcraft.yaml
Normal file
|
@ -0,0 +1,31 @@
|
|||
name: svls
|
||||
version: &version v0.1.0
|
||||
summary: SystemVerilog language server
|
||||
description: |
|
||||
A language server supporting SystemVerilog ( IEEE Std. 1800-2017 )
|
||||
base: core18
|
||||
license: MIT
|
||||
|
||||
confinement: strict
|
||||
|
||||
architectures:
|
||||
- build-on: amd64
|
||||
- build-on: i386
|
||||
- build-on: ppc64el
|
||||
- build-on: arm64
|
||||
- build-on: armhf
|
||||
|
||||
apps:
|
||||
svls:
|
||||
command: svls
|
||||
|
||||
parts:
|
||||
svls:
|
||||
source: https://github.com/dalance/svls.git
|
||||
source-tag: *version
|
||||
plugin: rust
|
||||
stage-packages:
|
||||
- libc6
|
||||
- libgcc1
|
||||
- libstdc++6
|
||||
- zlib1g
|
206
src/backend.rs
Normal file
206
src/backend.rs
Normal file
|
@ -0,0 +1,206 @@
|
|||
use futures::future;
|
||||
use jsonrpc_core::{BoxFuture, Result};
|
||||
use log::debug;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use sv_parser::parse_sv_str;
|
||||
use svlint::linter::Linter;
|
||||
use tower_lsp::lsp_types::*;
|
||||
use tower_lsp::{LanguageServer, Printer};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Backend {
|
||||
root_path: Arc<RwLock<Option<String>>>,
|
||||
root_uri: Arc<RwLock<Option<Url>>>,
|
||||
linter: Arc<RwLock<Option<Linter>>>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
fn lint(&self, s: &str) -> Vec<Diagnostic> {
|
||||
let mut ret = Vec::new();
|
||||
let parsed = parse_sv_str(
|
||||
s,
|
||||
&PathBuf::from(""),
|
||||
&HashMap::new(),
|
||||
&Vec::<PathBuf>::new(),
|
||||
);
|
||||
match parsed {
|
||||
Ok((syntax_tree, _new_defines)) => {
|
||||
let linter = self.linter.read().unwrap();
|
||||
if let Some(ref linter) = *linter {
|
||||
for node in &syntax_tree {
|
||||
for failed in linter.check(&syntax_tree, &node) {
|
||||
debug!("{:?}", failed);
|
||||
let (line, col) = get_position(s, failed.beg);
|
||||
ret.push(Diagnostic::new(
|
||||
Range::new(
|
||||
Position::new(line, col),
|
||||
Position::new(line, col + failed.len as u64),
|
||||
),
|
||||
Some(DiagnosticSeverity::Warning),
|
||||
Some(NumberOrString::String(String::from(failed.name))),
|
||||
Some(String::from("svls")),
|
||||
String::from(failed.hint),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageServer for Backend {
|
||||
type ShutdownFuture = BoxFuture<()>;
|
||||
type SymbolFuture = BoxFuture<Option<Vec<SymbolInformation>>>;
|
||||
type ExecuteFuture = BoxFuture<Option<Value>>;
|
||||
type CompletionFuture = BoxFuture<Option<CompletionResponse>>;
|
||||
type HoverFuture = BoxFuture<Option<Hover>>;
|
||||
type HighlightFuture = BoxFuture<Option<Vec<DocumentHighlight>>>;
|
||||
|
||||
fn initialize(&self, _printer: &Printer, params: InitializeParams) -> Result<InitializeResult> {
|
||||
debug!("root_path: {:?}", params.root_path);
|
||||
debug!("root_uri: {:?}", params.root_uri);
|
||||
|
||||
let config_svlint = search_config(&PathBuf::from(".svlint.toml"));
|
||||
debug!("config_svlint: {:?}", config_svlint);
|
||||
let linter = if let Some(config) = config_svlint {
|
||||
let mut f = File::open(&config).unwrap();
|
||||
let mut s = String::new();
|
||||
let _ = f.read_to_string(&mut s);
|
||||
let config = toml::from_str(&s).unwrap();
|
||||
Some(Linter::new(config))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut w = self.root_path.write().unwrap();
|
||||
*w = params.root_path.clone();
|
||||
|
||||
let mut w = self.root_uri.write().unwrap();
|
||||
*w = params.root_uri.clone();
|
||||
|
||||
let mut w = self.linter.write().unwrap();
|
||||
*w = linter;
|
||||
|
||||
Ok(InitializeResult {
|
||||
capabilities: ServerCapabilities {
|
||||
text_document_sync: Some(TextDocumentSyncCapability::Kind(
|
||||
TextDocumentSyncKind::Full,
|
||||
)),
|
||||
hover_provider: Some(true),
|
||||
completion_provider: Some(CompletionOptions {
|
||||
resolve_provider: Some(false),
|
||||
trigger_characters: Some(vec![".".to_string()]),
|
||||
}),
|
||||
signature_help_provider: Some(SignatureHelpOptions {
|
||||
trigger_characters: None,
|
||||
}),
|
||||
document_highlight_provider: Some(false),
|
||||
workspace_symbol_provider: Some(true),
|
||||
execute_command_provider: Some(ExecuteCommandOptions {
|
||||
commands: vec!["dummy.do_something".to_string()],
|
||||
}),
|
||||
workspace: Some(WorkspaceCapability {
|
||||
workspace_folders: Some(WorkspaceFolderCapability {
|
||||
supported: Some(true),
|
||||
change_notifications: Some(
|
||||
WorkspaceFolderCapabilityChangeNotifications::Bool(true),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
..ServerCapabilities::default()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn initialized(&self, printer: &Printer, _: InitializedParams) {
|
||||
printer.log_message(MessageType::Info, &format!("server initialized"));
|
||||
}
|
||||
|
||||
fn shutdown(&self) -> Self::ShutdownFuture {
|
||||
Box::new(future::ok(()))
|
||||
}
|
||||
|
||||
fn symbol(&self, _: WorkspaceSymbolParams) -> Self::SymbolFuture {
|
||||
Box::new(future::ok(None))
|
||||
}
|
||||
|
||||
fn did_change_workspace_folders(&self, _: &Printer, _: DidChangeWorkspaceFoldersParams) {}
|
||||
|
||||
fn did_change_configuration(&self, _: &Printer, _: DidChangeConfigurationParams) {}
|
||||
|
||||
fn did_change_watched_files(&self, _: &Printer, _: DidChangeWatchedFilesParams) {}
|
||||
|
||||
fn execute_command(&self, _: &Printer, _: ExecuteCommandParams) -> Self::ExecuteFuture {
|
||||
Box::new(future::ok(None))
|
||||
}
|
||||
|
||||
fn did_open(&self, printer: &Printer, params: DidOpenTextDocumentParams) {
|
||||
let diag = self.lint(¶ms.text_document.text);
|
||||
printer.publish_diagnostics(params.text_document.uri, diag);
|
||||
}
|
||||
|
||||
fn did_change(&self, printer: &Printer, params: DidChangeTextDocumentParams) {
|
||||
let diag = self.lint(¶ms.content_changes[0].text);
|
||||
printer.publish_diagnostics(params.text_document.uri, diag);
|
||||
}
|
||||
|
||||
fn did_save(&self, _: &Printer, _: DidSaveTextDocumentParams) {}
|
||||
|
||||
fn did_close(&self, _: &Printer, _: DidCloseTextDocumentParams) {}
|
||||
|
||||
fn completion(&self, _: CompletionParams) -> Self::CompletionFuture {
|
||||
Box::new(future::ok(None))
|
||||
}
|
||||
|
||||
fn hover(&self, _: TextDocumentPositionParams) -> Self::HoverFuture {
|
||||
Box::new(future::ok(None))
|
||||
}
|
||||
|
||||
fn document_highlight(&self, _: TextDocumentPositionParams) -> Self::HighlightFuture {
|
||||
Box::new(future::ok(None))
|
||||
}
|
||||
}
|
||||
|
||||
fn search_config(config: &Path) -> Option<PathBuf> {
|
||||
if let Ok(current) = env::current_dir() {
|
||||
for dir in current.ancestors() {
|
||||
let candidate = dir.join(config);
|
||||
if candidate.exists() {
|
||||
return Some(candidate);
|
||||
}
|
||||
}
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_position(s: &str, pos: usize) -> (u64, u64) {
|
||||
let mut line = 0;
|
||||
let mut col = 0;
|
||||
let mut p = 0;
|
||||
while p < pos {
|
||||
if let Some(c) = s.get(p..p + 1) {
|
||||
if c == "\n" {
|
||||
line += 1;
|
||||
col = 0;
|
||||
} else {
|
||||
col += 1;
|
||||
}
|
||||
} else {
|
||||
col += 1;
|
||||
}
|
||||
p += 1;
|
||||
}
|
||||
(line, col)
|
||||
}
|
51
src/main.rs
Normal file
51
src/main.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
mod backend;
|
||||
|
||||
use backend::Backend;
|
||||
use log::debug;
|
||||
use simplelog::{Config, LevelFilter, WriteLogger};
|
||||
use std::fs::File;
|
||||
use structopt::{clap, StructOpt};
|
||||
use tower_lsp::{LspService, Server};
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Opt
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "svls")]
|
||||
#[structopt(long_version(option_env!("LONG_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))))]
|
||||
#[structopt(setting(clap::AppSettings::ColoredHelp))]
|
||||
pub struct Opt {
|
||||
/// Debug mode
|
||||
#[structopt(short = "d", long = "debug")]
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Main
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
fn main() {
|
||||
let opt = Opt::from_args();
|
||||
|
||||
if opt.debug {
|
||||
let _ = WriteLogger::init(
|
||||
LevelFilter::Debug,
|
||||
Config::default(),
|
||||
File::create("svls.log").unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
debug!("start");
|
||||
|
||||
let stdin = tokio::io::stdin();
|
||||
let stdout = tokio::io::stdout();
|
||||
|
||||
let (service, messages) = LspService::new(Backend::default());
|
||||
let handle = service.close_handle();
|
||||
let server = Server::new(stdin, stdout)
|
||||
.interleave(messages)
|
||||
.serve(service);
|
||||
|
||||
tokio::run(handle.run_until_exit(server));
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue