Initial commit

This commit is contained in:
dalance 2019-11-15 14:35:48 +09:00
commit a05dd7e3f3
14 changed files with 2107 additions and 0 deletions

3
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: dalance

25
.github/workflows/periodic.yml vendored Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
/target
**/*.rs.bk

3
CHANGELOG.md Normal file
View 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

File diff suppressed because it is too large Load diff

38
Cargo.toml Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,46 @@
# svls
SystemVerilog language server
[![Actions Status](https://github.com/dalance/svls/workflows/Regression/badge.svg)](https://github.com/dalance/svls/actions)
[![Snap Status](https://build.snapcraft.io/badge/dalance/svls.svg)](https://build.snapcraft.io/user/dalance/svls)
[![Crates.io](https://img.shields.io/crates/v/svls.svg)](https://crates.io/crates/svls)
[![svlint](https://snapcraft.io/svls/badge.svg)](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
View 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
View 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(&params.text_document.text);
printer.publish_diagnostics(params.text_document.uri, diag);
}
fn did_change(&self, printer: &Printer, params: DidChangeTextDocumentParams) {
let diag = self.lint(&params.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
View 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));
}