Skip to content

Conversation

@tachyons
Copy link

@tachyons tachyons commented Oct 27, 2025

Add GitLab Duo Language Server Configuration

Summary

This PR adds native support for the GitLab Duo Language Server to the Neovim LSP configuration collection, enabling AI-powered code suggestions from GitLab Duo directly in Neovim.

Motivation

GitLab Duo provides AI-assisted code completion similar to GitHub Copilot, but requires a specific LSP setup with OAuth device flow authentication. This configuration makes it easy for Neovim users to integrate GitLab Duo into their workflow without manual LSP setup.

Features

🔐 OAuth Device Flow Authentication

  • Full implementation of GitLab's OAuth device flow
  • Automatic token refresh before expiration
  • Secure token storage in Neovim's data directory
  • No manual PAT management required

🔄 Automatic Token Management

  • Detects expired tokens and refreshes automatically
  • Handles token validation errors from the server
  • Provides clear status messages and error handling

🎮 User Commands

  • :LspGitLabDuoSignIn - Start OAuth authentication flow
  • :LspGitLabDuoSignOut - Sign out and remove stored token
  • :LspGitLabDuoStatus - View authentication and token status

⌨️ Standard Keybindings

  • Tab - Accept suggestion
  • Alt/Option+[ - Previous suggestion
  • Alt/Option+] - Next suggestion

Implementation Details

Authentication Flow

  1. User runs :LspGitLabDuoSignIn
  2. Device code is copied to clipboard automatically
  3. Browser opens to GitLab verification URL
  4. User authorizes the application
  5. Token is automatically retrieved and stored
  6. LSP configuration is updated with the new token

Token Lifecycle

  • Access tokens are stored with refresh tokens and expiration times
  • Tokens are automatically refreshed 60 seconds before expiry
  • Failed refresh attempts prompt re-authentication
  • Token validation errors trigger automatic refresh attempts

LSP Handlers

  • $/gitlab/token/check - Handles token validation errors
  • $/gitlab/featureStateChange - Notifies about feature availability
  • Workspace configuration updates for token changes

Dependencies

Required:

Runtime:

  • Node.js and npm (for GitLab LSP server)
  • GitLab account with Duo Pro license
  • Internet connection for OAuth flow

Installation & Usage

Basic Setup

-- Enable the LSP
vim.lsp.enable('gitlab_duo')

-- Setup keybindings for inline completion
vim.api.nvim_create_autocmd('LspAttach', {
  callback = function(args)
    local client = vim.lsp.get_client_by_id(args.data.client_id)
    local bufnr = args.buf
    
    if client.name == 'gitlab_duo' and 
        vim.lsp.inline_completion  and
       client:supports_method(vim.lsp.protocol.Methods.textDocument_inlineCompletion, bufnr) then
      vim.lsp.inline_completion.enable(true, { bufnr = bufnr })
      
      -- Tab to accept suggestion
      vim.keymap.set('i', '<Tab>', function()
        if vim.lsp.inline_completion.is_visible() then
          return vim.lsp.inline_completion.accept()
        else
          return '<Tab>'
        end
      end, { expr = true, buffer = bufnr })
      
      -- Navigate suggestions
      vim.keymap.set('i', '<M-[>', vim.lsp.inline_completion.select_prev, { buffer = bufnr })
      vim.keymap.set('i', '<M-]>', vim.lsp.inline_completion.select_next, { buffer = bufnr })
    end
  end
})

First-Time Authentication

:GitLabDuoSignIn

Follow the browser prompts to authorize the application.

Testing

Manual Testing Steps

  1. Install dependencies:

    -- With lazy.nvim
    { 'nvim-lua/plenary.nvim' }
  2. Load the configuration:

    vim.lsp.enable('gitlab_duo')
  3. Authenticate:

    :LspGitLabDuoSignIn
  4. Test suggestions:

    • Open a supported file (.lua, .py, .js, etc.)
    • Start typing code
    • Verify suggestions appear
    • Test Tab to accept
    • Test Alt+[ and Alt+] to cycle
  5. Test token management:

    :LspGitLabDuoStatus    " View authentication status
    :LspGitLabDuoSignOut   " Sign out
    :LspGitLabDuoSignIn    " Sign back in

Expected Behavior

  • ✅ OAuth flow completes successfully
  • ✅ Code suggestions appear while typing
  • ✅ Token refreshes automatically before expiry
  • ✅ Error messages are clear and actionable
  • ✅ Commands work in all supported file types

Configuration Options

Users can customize the GitLab instance URL via environment variable:

export GITLAB_URL="https://gitlab.example.com"

Default is https://gitlab.com.

Supported Languages

Ruby, Go, JavaScript, TypeScript, React, Rust, Lua, Python, Java, C, C++, PHP, C#, Kotlin, Swift, Scala, Vue, Svelte, HTML, CSS, SCSS, JSON, YAML

Documentation

Checklist

  • OAuth device flow implementation
  • Automatic token refresh
  • Error handling and user notifications
  • User commands for auth management
  • LSP handlers for GitLab-specific messages
  • Documentation and usage examples
  • Support for self-hosted GitLab instances

Screenshots

Screenshot 2025-10-27 at 1 17 41 PM Screenshot 2025-10-27 at 1 17 59 PM

Note: This configuration requires Neovim 0.12+ for native inline completion support.

@tachyons tachyons marked this pull request as draft October 27, 2025 07:55
This file contains the GitLab Duo Language Server configuration for Neovim,
including setup instructions, token management, and OAuth device flow.
@tachyons tachyons marked this pull request as ready for review October 27, 2025 13:43
--- })
--- ```

local curl = require('plenary.curl')
Copy link
Member

@justinmk justinmk Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvim 0.12 has vim.net.request but currently does not support POST :(

We have gone to a lot of trouble to keep all the configs in this repo totally "isolated" (self-contained), so would like to avoid a dependency on plenary.

Writing a curl wrapper for the purposes of this config using vim.system should not be much code.

Alternatively, we are interested in gaining POST support for vim.net.request if you want to work on that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed plenary dependency, thanks to Claude. I'm new to lua, So I may not be able to make substantial contributions for now

--- local bufnr = args.buf
--- local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
---
--- if client.name == 'gitlab_duo' and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we drop this condition? similar to https://github.com/neovim/nvim-lspconfig/blob/master/lsp/copilot.lua , there is nothing service-specific about inline_completion. The desc field should be generic like in the copilot config.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@justinmk It is now removed, I was bit worried if it can have unexpected behaviours if we haven't added allow list of client names.


-- Configuration
local config = {
gitlab_url = vim.env.GITLAB_URL or 'https://gitlab.com',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just mentioning: users can provide arbitrary fields using vim.lsp.config():

vim.lsp.config('gitlab_duo', {
  gitlab_duo = {
    gitlab_url = '...',
  },
})

Then in callbacks such as cmd() in this config, which are passed a config object, you can access those fields:

cmd = function(..., config)
  local foo = config.gitlab_duo
  ...
end)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@justinmk I couldn't get this working, cmd expects a table, not a function, right ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants