Skip to content

Commit

Permalink
Merge branch 'project-directory' into pm-master-commits
Browse files Browse the repository at this point in the history
  • Loading branch information
puremourning committed Feb 9, 2020
2 parents 6e2e10b + 8a07dff commit 2c01d0f
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 46 deletions.
12 changes: 4 additions & 8 deletions ycmd/completers/cpp/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import os
import inspect
from ycmd import extra_conf_store
from ycmd.utils import ( OnMac,
from ycmd.utils import ( AbsoluatePath,
OnMac,
OnWindows,
PathsToAllParentFolders,
re,
Expand Down Expand Up @@ -616,9 +617,7 @@ def _MakeRelativePathsInFlagsAbsolute( flags, working_directory ):

if make_next_absolute:
make_next_absolute = False
if not os.path.isabs( new_flag ):
new_flag = os.path.join( working_directory, flag )
new_flag = os.path.normpath( new_flag )
new_flag = AbsoluatePath( flag, working_directory )
else:
for path_flag in path_flags:
# Single dash argument alone, e.g. -isysroot <path>
Expand All @@ -630,10 +629,7 @@ def _MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
# or double-dash argument, e.g. --isysroot=<path>
if flag.startswith( path_flag ):
path = flag[ len( path_flag ): ]
if not os.path.isabs( path ):
path = os.path.join( working_directory, path )
path = os.path.normpath( path )

path = AbsoluatePath( path, working_directory )
new_flag = '{0}{1}'.format( path_flag, path )
break

Expand Down
8 changes: 5 additions & 3 deletions ycmd/completers/go/go_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def SupportedFiletypes( self ):


def GetDoc( self, request_data ):
assert self._settings[ 'hoverKind' ] == 'Structured'
assert self._settings[ 'ls' ][ 'hoverKind' ] == 'Structured'
try:
result = json.loads( self.GetHoverResponse( request_data )[ 'value' ] )
docs = result[ 'signature' ] + '\n' + result[ 'fullDocumentation' ]
Expand All @@ -92,5 +92,7 @@ def GetType( self, request_data ):


def DefaultSettings( self, request_data ):
return { 'hoverKind': 'Structured',
'fuzzyMatching': False }
return {
'hoverKind': 'Structured',
'fuzzyMatching': False,
}
4 changes: 4 additions & 0 deletions ycmd/completers/java/java_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,10 @@ def StartServer( self,

if project_directory:
self._java_project_dir = project_directory
elif 'project_directory' in self._settings:
self._java_project_dir = utils.AbsoluatePath(
self._settings[ 'project_directory' ],
self._extra_conf_dir )
else:
self._java_project_dir = _FindProjectDir(
os.path.dirname( request_data[ 'filepath' ] ) )
Expand Down
67 changes: 47 additions & 20 deletions ycmd/completers/language_server/language_server_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@ def ServerReset( self ):
self._resolve_completion_items = False
self._project_directory = None
self._settings = {}
self._extra_conf_dir = None
self._server_started = False


Expand Down Expand Up @@ -1272,19 +1273,30 @@ def GetSettings( self, module, request_data ):


def _GetSettingsFromExtraConf( self, request_data ):
self._settings = self.DefaultSettings( request_data )
# The default settings method returns only the 'language server" ('ls')
# settings, but self._settings is a wider dict containing a 'ls' key and any
# other keys that we might want to add (e.g. 'project_directory',
# 'capabilities', etc.)
ls = self.DefaultSettings( request_data )
self._settings = {
'ls': ls
}

module = extra_conf_store.ModuleForSourceFile( request_data[ 'filepath' ] )
if module:
settings = self.GetSettings( module, request_data )
self._settings.update( settings.get( 'ls', {} ) )
self._settings = self.GetSettings( module, request_data )
# Overlay the supplied language server ('ls') settings on the defaults
ls.update( self._settings.get( 'ls', {} ) )
self._settings[ 'ls' ] = ls

# Only return the dir if it was found in the paths; we don't want to use
# the path of the global extra conf as a project root dir.
if not extra_conf_store.IsGlobalExtraConfModule( module ):
LOGGER.debug( 'Using path %s for extra_conf_dir',
os.path.dirname( module.__file__ ) )
return os.path.dirname( module.__file__ )

# No local extra conf
return None


Expand All @@ -1294,14 +1306,14 @@ def _StartAndInitializeServer( self, request_data, *args, **kwargs ):
StartServer. In general, completers don't need to call this as it is called
automatically in OnFileReadyToParse, but this may be used in completer
subcommands that require restarting the underlying server."""
extra_conf_dir = self._GetSettingsFromExtraConf( request_data )
self._extra_conf_dir = self._GetSettingsFromExtraConf( request_data )

# Only attempt to start the server once. Set this after above call as it may
# throw an exception
self._server_started = True

if self.StartServer( request_data, *args, **kwargs ):
self._SendInitialize( request_data, extra_conf_dir )
self._SendInitialize( request_data )


def OnFileReadyToParse( self, request_data ):
Expand Down Expand Up @@ -1642,18 +1654,29 @@ def GetProjectRootFiles( self ):
GetProjectDirectory."""
return []

def GetProjectDirectory( self, request_data, extra_conf_dir ):

def GetProjectDirectory( self, request_data ):
"""Return the directory in which the server should operate. Language server
protocol and most servers have a concept of a 'project directory'. Where a
concrete completer can detect this better, it should override this method,
but otherwise, we default as follows:
- If the user specified 'project_directory' in their extra conf
'Settings', use that.
- try to find files from GetProjectRootFiles and use the
first directory from there
- if there's an extra_conf file, use that directory
- otherwise if we know the client's cwd, use that
- otherwise use the diretory of the file that we just opened
Note: None of these are ideal. Ycmd doesn't really have a notion of project
directory and therefore neither do any of our clients."""
directory and therefore neither do any of our clients.
NOTE: Must be called _after_ _GetSettingsFromExtraConf, as it uses
self._settings and self._extra_conf_dir
"""

if 'project_directory' in self._settings:
return utils.AbsoluatePath( self._settings[ 'project_directory' ],
self._extra_conf_dir )

project_root_files = self.GetProjectRootFiles()
if project_root_files:
Expand All @@ -1662,30 +1685,29 @@ def GetProjectDirectory( self, request_data, extra_conf_dir ):
if os.path.isfile( os.path.join( folder, root_file ) ):
return folder

if extra_conf_dir:
return extra_conf_dir
if self._extra_conf_dir:
return self._extra_conf_dir

if 'working_dir' in request_data:
return request_data[ 'working_dir' ]

return os.path.dirname( request_data[ 'filepath' ] )


def _SendInitialize( self, request_data, extra_conf_dir ):
def _SendInitialize( self, request_data ):
"""Sends the initialize request asynchronously.
This must be called immediately after establishing the connection with the
language server. Implementations must not issue further requests to the
server until the initialize exchange has completed. This can be detected by
calling this class's implementation of _ServerIsInitialized.
The extra_conf_dir parameter is the value returned from
_GetSettingsFromExtraConf, which must be called before calling this method.
_GetSettingsFromExtraConf must be called before calling this method, as this
method release on self._extra_conf_dir.
It is called before starting the server in OnFileReadyToParse."""

with self._server_info_mutex:
assert not self._initialize_response

self._project_directory = self.GetProjectDirectory( request_data,
extra_conf_dir )
self._project_directory = self.GetProjectDirectory( request_data )
request_id = self.GetConnection().NextRequestId()

# FIXME: According to the discussion on
Expand All @@ -1695,7 +1717,7 @@ def _SendInitialize( self, request_data, extra_conf_dir ):
# clear how/where that is specified.
msg = lsp.Initialize( request_id,
self._project_directory,
self._settings )
self._settings.get( 'ls', {} ) )

def response_handler( response, message ):
if message is None:
Expand Down Expand Up @@ -1808,7 +1830,7 @@ def _HandleInitializeInPollThread( self, response ):
# configuration should be send in response to a workspace/configuration
# request?
self.GetConnection().SendNotification(
lsp.DidChangeConfiguration( self._settings ) )
lsp.DidChangeConfiguration( self._settings.get( 'ls', {} ) ) )

# Notify the other threads that we have completed the initialize exchange.
self._initialize_response = None
Expand Down Expand Up @@ -2053,6 +2075,10 @@ def RefactorRename( self, request_data, args ):


def AdditionalFormattingOptions( self, request_data ):
# While we have the settings in self._settings[ 'formatting_options' ], we
# actually run Settings again here, which allows users to have different
# formatting options for different files etc. if they should decide that's
# appropriate.
module = extra_conf_store.ModuleForSourceFile( request_data[ 'filepath' ] )
try:
settings = self.GetSettings( module, request_data )
Expand Down Expand Up @@ -2180,10 +2206,11 @@ def ServerStateDescription():
ServerStateDescription() ),
responses.DebugInfoItem( 'Project Directory',
self._project_directory ),
responses.DebugInfoItem( 'Settings',
json.dumps( self._settings,
indent = 2,
sort_keys = True ) ) ]
responses.DebugInfoItem(
'Settings',
json.dumps( self._settings.get( 'ls', {} ),
indent = 2,
sort_keys = True ) ) ]


def _DistanceOfPointToRange( point, range ):
Expand Down
7 changes: 6 additions & 1 deletion ycmd/tests/java/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,15 @@ def teardown_module():


def StartJavaCompleterServerInDirectory( app, directory ):
StartJavaCompleterServerWithFile( app,
os.path.join( directory, 'test.java' ) )


def StartJavaCompleterServerWithFile( app, file_path ):
app.post_json( '/event_notification',
BuildRequest(
filepath = os.path.join( directory, 'test.java' ),
event_name = 'FileReadyToParse',
filepath = file_path,
filetype = 'java' ) )
WaitUntilCompleterServerReady( app, 'java', SERVER_STARTUP_TIMEOUT )

Expand Down
26 changes: 25 additions & 1 deletion ycmd/tests/java/server_management_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
from ycmd.tests.java import ( PathToTestFile,
IsolatedYcmd,
SharedYcmd,
StartJavaCompleterServerInDirectory )
StartJavaCompleterServerInDirectory,
StartJavaCompleterServerWithFile )
from ycmd.tests.test_utils import ( BuildRequest,
CompleterProjectDirectoryMatcher,
ErrorMatcher,
Expand Down Expand Up @@ -215,6 +216,29 @@ def ServerManagement_WipeWorkspace_WithConfig_test( isolated_app ):
) )


@IsolatedYcmd( {
'extra_conf_globlist': PathToTestFile( 'multiple_projects', '*' )
} )
def ServerManagement_ProjectDetection_MultipleProjects_test( app ):
# The ycm_extra_conf.py file should set the project path to
# multiple_projects/src
project = PathToTestFile( 'multiple_projects', 'src' )
StartJavaCompleterServerWithFile( app,
os.path.join( project,
'core',
'java',
'com',
'puremourning',
'widget',
'core',
'Utils.java' ) )

# Run the debug info to check that we have the correct project dir
request_data = BuildRequest( filetype = 'java' )
assert_that( app.post_json( '/debug_info', request_data ).json,
CompleterProjectDirectoryMatcher( project ) )


@IsolatedYcmd()
def ServerManagement_ProjectDetection_EclipseParent_test( app ):
StartJavaCompleterServerInDirectory(
Expand Down
51 changes: 50 additions & 1 deletion ycmd/tests/java/subcommands_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@

from ycmd.utils import ReadFile
from ycmd.completers.java.java_completer import NO_DOCUMENTATION_MESSAGE
from ycmd.tests.java import PathToTestFile, SharedYcmd, IsolatedYcmd
from ycmd.tests.java import ( PathToTestFile,
SharedYcmd,
StartJavaCompleterServerWithFile,
IsolatedYcmd )
from ycmd.tests.test_utils import ( BuildRequest,
ChunkMatcher,
CombineRequest,
Expand Down Expand Up @@ -549,6 +552,52 @@ def Subcommands_GoToReferences_NoReferences_test( app ):
'Cannot jump to location' ) )


@WithRetry
@IsolatedYcmd( {
'extra_conf_globlist': PathToTestFile( 'multiple_projects', '*' )
} )
def Subcommands_GoToReferences_MultipleProjects_test( app ):
filepath = PathToTestFile( 'multiple_projects',
'src',
'core',
'java',
'com',
'puremourning',
'widget',
'core',
'Utils.java' )
StartJavaCompleterServerWithFile( app, filepath )


RunTest( app, {
'description': 'GoToReferences works across multiple projects',
'request': {
'command': 'GoToReferences',
'filepath': filepath,
'line_num': 5,
'column_num': 22,
},
'expect': {
'response': requests.codes.ok,
'data': contains_inanyorder(
LocationMatcher( filepath, 8, 35 ),
LocationMatcher( PathToTestFile( 'multiple_projects',
'src',
'input',
'java',
'com',
'puremourning',
'widget',
'input',
'InputApp.java' ),
8,
16 )
)
}
} )



@WithRetry
@SharedYcmd
def Subcommands_GoToReferences_test( app ):
Expand Down
4 changes: 4 additions & 0 deletions ycmd/tests/java/testdata/multiple_projects/.ycm_extra_conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def Settings( **kwargs ):
return {
'project_directory': './src'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="java" />
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
23 changes: 23 additions & 0 deletions ycmd/tests/java/testdata/multiple_projects/src/core/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is the minimal project configuration (combined with .cloasspath) that
allow JDT LS to fully work with a trivial eclipse project.
Specification:
https://help.eclipse.org/oxygen/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fmisc%2Fproject_description_file.html
-->
<projectDescription>
<name>com.puremourning.widget.core</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
Loading

0 comments on commit 2c01d0f

Please sign in to comment.