Skip to content

Commit

Permalink
Merge branch 'project-directory'
Browse files Browse the repository at this point in the history
  • Loading branch information
puremourning committed Dec 21, 2019
2 parents b94164a + 89364d1 commit 11eb4ba
Show file tree
Hide file tree
Showing 16 changed files with 235 additions and 42 deletions.
12 changes: 4 additions & 8 deletions ycmd/completers/cpp/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
import inspect
from future.utils import PY2, native
from ycmd import extra_conf_store
from ycmd.utils import ( OnMac,
from ycmd.utils import ( AbsoluatePath,
OnMac,
OnWindows,
PathsToAllParentFolders,
re,
Expand Down Expand Up @@ -636,9 +637,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 @@ -650,10 +649,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 @@ -84,7 +84,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 @@ -103,5 +103,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 @@ -448,6 +448,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
52 changes: 37 additions & 15 deletions ycmd/completers/language_server/language_server_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,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 @@ -1283,6 +1284,10 @@ def GetSettings( self, module, request_data ):


def _GetSettingsFromExtraConf( self, 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 )
# In case no module is found
self._settings = {
Expand All @@ -1292,6 +1297,7 @@ def _GetSettingsFromExtraConf( self, request_data ):
module = extra_conf_store.ModuleForSourceFile( request_data[ 'filepath' ] )
if module:
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

Expand All @@ -1302,6 +1308,7 @@ def _GetSettingsFromExtraConf( self, request_data ):
os.path.dirname( module.__file__ ) )
return os.path.dirname( module.__file__ )

# No local extra conf
return None


Expand All @@ -1311,14 +1318,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 @@ -1659,18 +1666,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 @@ -1679,30 +1697,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 Down Expand Up @@ -2073,6 +2090,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 @@ -2200,10 +2221,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/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,15 @@ def tearDownPackage():


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 @@ -34,7 +34,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 @@ -192,6 +193,29 @@ def ServerManagement_WipeWorkspace_WithConfig( app ):
yield ServerManagement_WipeWorkspace_WithConfig


@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
47 changes: 47 additions & 0 deletions ycmd/tests/java/subcommands_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from ycmd.tests.java import ( DEFAULT_PROJECT_DIR,
PathToTestFile,
SharedYcmd,
StartJavaCompleterServerWithFile,
IsolatedYcmd )
from ycmd.tests.test_utils import ( BuildRequest,
ChunkMatcher,
Expand Down Expand Up @@ -548,6 +549,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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.puremourning.widget.core;

public class Utils
{
public static int Test = 100;

public static void DoSomething() {
System.out.println( "test " + Test );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="java" />
<classpathentry exported="true" kind="src" path="/com.puremourning.widget.core/" />
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
Loading

0 comments on commit 11eb4ba

Please sign in to comment.