diff --git a/autoload/LanguageClient.vim b/autoload/LanguageClient.vim
index 722a8733..d52db5b5 100644
--- a/autoload/LanguageClient.vim
+++ b/autoload/LanguageClient.vim
@@ -8,6 +8,7 @@ let s:TYPE = {
 \   'dict':    type({}),
 \   'funcref': type(function('call'))
 \ }
+let s:FLOAT_WINDOW_AVAILABLE = has('nvim') && exists('*nvim_open_win')
 
 function! s:AddPrefix(message) abort
     return '[LC] ' . a:message
@@ -261,6 +262,150 @@ function! s:GetVar(...) abort
     endif
 endfunction
 
+function! s:ShouldUseFloatWindow() abort
+    let use = s:GetVar('LanguageClient_useFloatingHover')
+    return s:FLOAT_WINDOW_AVAILABLE && (use || use is v:null)
+endfunction
+
+function! s:CloseFloatingHoverOnCursorMove(win_id, opened) abort
+    if getpos('.') == a:opened
+        " Just after opening floating window, CursorMoved event is run.
+        " To avoid closing floating window immediately, check the cursor
+        " was really moved
+        return
+    endif
+    autocmd! plugin-LC-neovim-close-hover
+    let winnr = win_id2win(a:win_id)
+    if winnr == 0
+        return
+    endif
+    execute winnr . 'wincmd c'
+endfunction
+
+function! s:CloseFloatingHoverOnBufEnter(win_id, bufnr) abort
+    let winnr = win_id2win(a:win_id)
+    if winnr == 0
+        " Float window was already closed
+        autocmd! plugin-LC-neovim-close-hover
+        return
+    endif
+    if winnr == winnr()
+        " Cursor is moving into floating window. Do not close it
+        return
+    endif
+    if bufnr('%') == a:bufnr
+        " When current buffer opened hover window, it's not another buffer. Skipped
+        return
+    endif
+    autocmd! plugin-LC-neovim-close-hover
+    execute winnr . 'wincmd c'
+endfunction
+
+" Open preview window. Window is open in:
+"   - Floating window on Neovim (0.4.0 or later)
+"   - Preview window on Neovim (0.3.0 or earlier) or Vim
+function! s:OpenHoverPreview(bufname, lines, filetype) abort
+    " Use local variable since parameter is not modifiable
+    let lines = a:lines
+    let bufnr = bufnr('%')
+
+    let use_float_win = s:ShouldUseFloatWindow()
+    if use_float_win
+        let pos = getpos('.')
+
+        " Calculate width and height and give margin to lines
+        let width = 0
+        for index in range(len(lines))
+            let line = lines[index]
+            if line !=# ''
+                " Give a left margin
+                let line = ' ' . line
+            endif
+            let lw = strdisplaywidth(line)
+            if lw > width
+                let width = lw
+            endif
+            let lines[index] = line
+        endfor
+
+        " Give margin
+        let width += 1
+        let lines = [''] + lines + ['']
+        let height = len(lines)
+
+        " Calculate anchor
+        " Prefer North, but if there is no space, fallback into South
+        let bottom_line = line('w0') + winheight(0) - 1
+        if pos[1] + height <= bottom_line
+            let vert = 'N'
+            let row = 1
+        else
+            let vert = 'S'
+            let row = 0
+        endif
+
+        " Prefer West, but if there is no space, fallback into East
+        if pos[2] + width <= &columns
+            let hor = 'W'
+            let col = 0
+        else
+            let hor = 'E'
+            let col = 1
+        endif
+
+        let float_win_id = nvim_open_win(bufnr, v:true, {
+        \   'relative': 'cursor',
+        \   'anchor': vert . hor,
+        \   'row': row,
+        \   'col': col,
+        \   'width': width,
+        \   'height': height,
+        \ })
+
+        execute 'noswapfile edit!' a:bufname
+
+        setlocal winhl=Normal:CursorLine
+    else
+        execute 'silent! noswapfile pedit!' a:bufname
+        wincmd P
+    endif
+
+    setlocal buftype=nofile nobuflisted bufhidden=wipe nonumber norelativenumber signcolumn=no
+
+    if a:filetype isnot v:null
+        let &filetype = a:filetype
+    endif
+
+    call setline(1, lines)
+    setlocal nomodified nomodifiable
+
+    wincmd p
+
+    if use_float_win
+        " Unlike preview window, :pclose does not close window. Instead, close
+        " hover window automatically when cursor is moved.
+        let call_after_move = printf('<SID>CloseFloatingHoverOnCursorMove(%d, %s)', float_win_id, string(pos))
+        let call_on_bufenter = printf('<SID>CloseFloatingHoverOnBufEnter(%d, %d)', float_win_id, bufnr)
+        augroup plugin-LC-neovim-close-hover
+            execute 'autocmd CursorMoved,CursorMovedI,InsertEnter <buffer> call ' . call_after_move
+            execute 'autocmd BufEnter * call ' . call_on_bufenter
+        augroup END
+    endif
+endfunction
+
+function! s:MoveIntoHoverPreview() abort
+    for bufnr in range(1, bufnr('$'))
+        if bufname(bufnr) ==# '__LanguageClient__'
+            let winnr = bufwinnr(bufnr)
+            if winnr != -1
+                execute winnr . 'wincmd w'
+            endif
+            return v:true
+        endif
+    endfor
+    return v:false
+endfunction
+
 let s:id = 1
 let s:handlers = {}
 
@@ -529,6 +674,9 @@ function! LanguageClient#Notify(method, params) abort
 endfunction
 
 function! LanguageClient#textDocument_hover(...) abort
+    if s:ShouldUseFloatWindow() && s:MoveIntoHoverPreview()
+        return
+    endif
     let l:Callback = get(a:000, 1, v:null)
     let l:params = {
                 \ 'filename': LSP#filename(),
diff --git a/autoload/health/LanguageClient.vim b/autoload/health/LanguageClient.vim
index 598c1f50..4cc5e7e3 100644
--- a/autoload/health/LanguageClient.vim
+++ b/autoload/health/LanguageClient.vim
@@ -15,11 +15,20 @@ function! s:checkBinary() abort
                     \ l:path)
     endif
 
-    let output = system([l:path, '--version'])
+    let output = substitute(system([l:path, '--version']), '\n$', '', '')
     call health#report_ok(output)
 endfunction
 
+function! s:checkFloatingWindow() abort
+    if !exists('*nvim_open_win')
+        call health#report_info('Floating window is not supported. Preview window will be used for hover')
+        return
+    endif
+    call health#report_ok('Floating window is supported and will be used for hover')
+endfunction
+
 function! health#LanguageClient#check() abort
     call s:checkJobFeature()
     call s:checkBinary()
+    call s:checkFloatingWindow()
 endfunction
diff --git a/doc/LanguageClient.txt b/doc/LanguageClient.txt
index b19ce815..3cd84dac 100644
--- a/doc/LanguageClient.txt
+++ b/doc/LanguageClient.txt
@@ -63,7 +63,6 @@ accessed by regular quickfix/location list operations.
 To use the language server with Vim's formatting operator |gq|, set 'formatexpr': >
     set formatexpr=LanguageClient#textDocument_rangeFormatting_sync()
 <
-
 ==============================================================================
 2. Configuration                                 *LanguageClientConfiguration*
 
@@ -348,6 +347,16 @@ Specify whether to use virtual text to display diagnostics.
 Default: 1 whenever virtual text is supported.
 Valid Options: 1 | 0
 
+2.26 g:LanguageClient_useFloatingHover *g:LanguageClient_useFloatingHover*
+
+When the value is set to 1, |LanguageClient#textDocument_hover()| opens
+documentation in a floating window instead of preview.
+This variable is effective only when the floating window feature is
+supported.
+
+Default: 1 when a floating window is supported, otherwise 0
+Valid Options: 1 | 0
+
 ==============================================================================
 3. Commands                                           *LanguageClientCommands*
 
@@ -397,6 +406,12 @@ Signature: LanguageClient#textDocument_hover(...)
 
 Show type info (and short doc) of identifier under cursor.
 
+If you're using Neovim 0.4.0 or later, this function opens documentation in a
+floating window. The window is automatically closed when you move the cursor.
+Or calling this function again just after opening the floating window moves
+the cursor into the window. It is useful when documentation is longer and you
+need to scroll down or you want to yank some text in the documentation.
+
 *LanguageClient#textDocument_definition()*
 *LanguageClient_textDocument_definition()*
 Signature: LanguageClient#textDocument_definition(...)
diff --git a/src/language_server_protocol.rs b/src/language_server_protocol.rs
index 163f4053..da8b1889 100644
--- a/src/language_server_protocol.rs
+++ b/src/language_server_protocol.rs
@@ -860,27 +860,12 @@ impl LanguageClient {
         D: ToDisplay + ?Sized,
     {
         let bufname = "__LanguageClient__";
-
-        let cmd = "silent! pedit! +setlocal\\ buftype=nofile\\ nobuflisted\\ noswapfile\\ nonumber";
-        let cmd = if let Some(ref ft) = to_display.vim_filetype() {
-            format!("{}\\ filetype={} {}", cmd, ft, bufname)
-        } else {
-            format!("{} {}", cmd, bufname)
-        };
-        self.vim()?.command(cmd)?;
-
+        let filetype = &to_display.vim_filetype();
         let lines = to_display.to_display();
-        if self.get(|state| state.is_nvim)? {
-            let bufnr: u64 = serde_json::from_value(self.vim()?.rpcclient.call("bufnr", bufname)?)?;
-            self.vim()?
-                .rpcclient
-                .notify("nvim_buf_set_lines", json!([bufnr, 0, -1, 0, lines]))?;
-        } else {
-            self.vim()?
-                .rpcclient
-                .notify("setbufline", json!([bufname, 1, lines]))?;
-            // TODO: removing existing bottom lines.
-        }
+
+        self.vim()?
+            .rpcclient
+            .notify("s:OpenHoverPreview", json!([bufname, lines, filetype]))?;
 
         Ok(())
     }
diff --git a/tests/LanguageClient_test.py b/tests/LanguageClient_test.py
index fb2b58c1..9273615a 100644
--- a/tests/LanguageClient_test.py
+++ b/tests/LanguageClient_test.py
@@ -208,3 +208,128 @@ def test_languageClient_registerHandlers(nvim):
 #                       if b.name.startswith('term://')), None) is None)
 
 #     assertRetry(lambda: len(nvim.funcs.getqflist()) == 0)
+
+
+def _open_float_window(nvim):
+    nvim.funcs.cursor(13, 19)
+    pos = nvim.funcs.getpos('.')
+    nvim.funcs.LanguageClient_textDocument_hover()
+    time.sleep(1)
+    return pos
+
+
+def test_textDocument_hover_float_window_closed_on_cursor_moved(nvim):
+    if not nvim.funcs.exists("*nvim_open_win"):
+        pytest.skip("Neovim 0.3.0 or earlier does not support floating window")
+
+    nvim.command("edit! {}".format(PATH_INDEXJS))
+    time.sleep(1)
+
+    buf = nvim.current.buffer
+
+    pos = _open_float_window(nvim)
+
+    float_buf = next(
+        b for b in nvim.buffers if b.name.endswith("__LanguageClient__"))
+
+    # Check if float window is open
+    float_winnr = nvim.funcs.bufwinnr(float_buf.number)
+    assert float_winnr > 0
+
+    # Check if cursor is not moved
+    assert buf.number == nvim.current.buffer.number
+    assert pos == nvim.funcs.getpos(".")
+
+    # Move cursor to left
+    nvim.funcs.cursor(13, 17)
+
+    # Check float window buffer was closed by CursorMoved
+    assert all(
+        b for b in nvim.buffers if not b.name.endswith("__LanguageClient__"))
+
+
+def test_textDocument_hover_float_window_closed_on_entering_window(nvim):
+    if not nvim.funcs.exists("*nvim_open_win"):
+        pytest.skip("Neovim 0.3.0 or earlier does not support floating window")
+
+    nvim.command("edit! {}".format(PATH_INDEXJS))
+    time.sleep(1)
+
+    win_id = nvim.funcs.win_getid()
+    nvim.command("split")
+    try:
+        assert win_id != nvim.funcs.win_getid()
+
+        _open_float_window(nvim)
+        assert win_id != nvim.funcs.win_getid()
+
+        # Move to another window
+        nvim.funcs.win_gotoid(win_id)
+        assert win_id == nvim.funcs.win_getid()
+
+        # Check float window buffer was closed by BufEnter
+        assert all(
+            b for b in nvim.buffers
+            if not b.name.endswith("__LanguageClient__"))
+    finally:
+        nvim.command("close!")
+
+
+def test_textDocument_hover_float_window_closed_on_switching_to_buffer(nvim):
+    if not nvim.funcs.exists("*nvim_open_win"):
+        pytest.skip("Neovim 0.3.0 or earlier does not support floating window")
+
+    # Create a new buffer
+    nvim.command("enew!")
+
+    another_bufnr = nvim.current.buffer.number
+
+    try:
+        nvim.command("edit! {}".format(PATH_INDEXJS))
+        time.sleep(1)
+
+        source_bufnr = nvim.current.buffer.number
+
+        _open_float_window(nvim)
+
+        float_buf = next(
+            b for b in nvim.buffers if b.name.endswith("__LanguageClient__"))
+        float_winnr = nvim.funcs.bufwinnr(float_buf.number)
+        assert float_winnr > 0
+
+        assert nvim.current.buffer.number == source_bufnr
+
+        # Move to another buffer within the same window
+        nvim.command("buffer {}".format(another_bufnr))
+        assert nvim.current.buffer.number == another_bufnr
+
+        # Check float window buffer was closed by BufEnter
+        assert all(
+            b for b in nvim.buffers
+            if not b.name.endswith("__LanguageClient__"))
+    finally:
+        nvim.command("bdelete! {}".format(another_bufnr))
+
+
+def test_textDocument_hover_float_window_move_cursor_into_window(nvim):
+    if not nvim.funcs.exists("*nvim_open_win"):
+        pytest.skip("Neovim 0.3.0 or earlier does not support floating window")
+
+    nvim.command("edit! {}".format(PATH_INDEXJS))
+    time.sleep(1)
+
+    prev_bufnr = nvim.current.buffer.number
+
+    _open_float_window(nvim)
+
+    # Moves cursor into floating window
+    nvim.funcs.LanguageClient_textDocument_hover()
+    assert nvim.current.buffer.name.endswith("__LanguageClient__")
+
+    # Close the window
+    nvim.command('close')
+    assert nvim.current.buffer.number == prev_bufnr
+
+    # Check float window buffer was closed by :close in the window
+    assert all(
+        b for b in nvim.buffers if not b.name.endswith("__LanguageClient__"))