Skip to content

[Story] LSP Phase 3: IDE workspace config support & hot-reloading #44

@ekropotin

Description

@ekropotin

User Story

As a developer using VS Code/other IDEs, I want to override QuickMark settings through my IDE's configuration system and have config changes applied immediately, so I can customize behavior without restarting the language server.

Acceptance Criteria

  • Accepts configuration via LSP initialization parameters from IDE settings
  • IDE settings override file-based config where applicable
  • Supports common IDE config patterns (nested objects, arrays)
  • Config file hot-reloading via file system watching
  • Re-applies config when quickmark.toml files change
  • Handles workspace/didChangeConfiguration LSP notifications
  • Configuration changes trigger re-linting of all open documents

Additional Context

Technical Implementation Details:

Configuration Priority Hierarchy (highest to lowest):

  1. IDE Workspace Settings (via LSP workspace/didChangeConfiguration)
  2. IDE User Settings (via LSP initialization params)
  3. Project Config File (quickmark.toml from Phase 2 discovery)
  4. Default Configuration

LSP Configuration Support:

// VS Code settings.json example
{
  "quickmark.rules.MD013.line_length": 100,
  "quickmark.rules.MD013.severity": "warning",
  "quickmark.configPath": "./custom-quickmark.toml"
}

Configuration Merging Strategy:

pub struct ConfigManager {
    // File-based config from Phase 2 discovery
    file_config: Option<QuickmarkConfig>,
    file_watcher: Option<RecommendedWatcher>,
    
    // IDE-provided config
    ide_init_config: Option<Value>,      // From initialize params
    ide_workspace_config: Option<Value>, // From didChangeConfiguration
    
    // Merged final config
    effective_config: QuickmarkConfig,
}

impl ConfigManager {
    pub fn merge_configs(&mut self) -> Result<QuickmarkConfig> {
        let mut config = self.file_config.clone().unwrap_or_default();
        
        // Apply IDE init settings
        if let Some(ide_init) = &self.ide_init_config {
            config = config.merge_with_ide_settings(ide_init)?;
        }
        
        // Apply IDE workspace settings (highest priority)
        if let Some(ide_workspace) = &self.ide_workspace_config {
            config = config.merge_with_ide_settings(ide_workspace)?;
        }
        
        Ok(config)
    }
}

File Watching Implementation:

use notify::{RecommendedWatcher, RecursiveMode, Event, EventKind};

impl ConfigManager {
    pub fn start_watching(&mut self, config_paths: Vec<PathBuf>) -> Result<()> {
        let (tx, rx) = channel();
        let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
        
        // Watch all discovered config files
        for path in config_paths {
            watcher.watch(&path, RecursiveMode::NonRecursive)?;
        }
        
        // Handle file change events
        tokio::spawn(async move {
            while let Ok(event) = rx.recv() {
                match event {
                    Ok(Event { kind: EventKind::Modify(_), paths, .. }) => {
                        // Config file changed - trigger reload
                        self.reload_config_files(paths).await;
                    }
                    _ => {}
                }
            }
        });
        
        Ok(())
    }
}

LSP Message Handling:

  • initialize → Extract IDE settings from initializationOptions
  • workspace/didChangeConfiguration → Update IDE workspace config, re-merge, re-lint all
  • File system events → Reload file config, re-merge, re-lint affected documents

IDE Integration Examples:

VS Code (package.json):

{
  "contributes": {
    "configuration": {
      "title": "QuickMark",
      "properties": {
        "quickmark.configPath": {
          "type": "string",
          "description": "Path to quickmark.toml configuration file"
        },
        "quickmark.rules": {
          "type": "object",
          "description": "Rule-specific overrides",
          "properties": {
            "MD013": {
              "type": "object",
              "properties": {
                "line_length": {"type": "number"},
                "severity": {"enum": ["off", "warning", "error"]}
              }
            }
          }
        }
      }
    }
  }
}

Dependencies Added:

notify = "6"                # File system watching
tokio = { version = "1", features = ["fs"] }

This is Phase 3 of a 6-phase LSP implementation plan for QuickMark.

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: triageNeeds review and prioritizationtype: storyNew feature or enhancement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions