Lsp: PHP support using bmewburn/intelephense

Created on 25 Jan 2018  Â·  28Comments  Â·  Source: sublimelsp/LSP

Current server (https://github.com/felixfbecker/php-language-server) explained in https://lsp.readthedocs.io/en/latest/ is very slow.
There are two projects for VS-Code that are much faster and complete:
1) https://github.com/bmewburn/intelephense (this seems to best one today)
2) https://github.com/HvyIndustries/crane

Thanks!

Most helpful comment

For documentation's sake, here is a configuration that allows a successful startup on OS-X after npm install -g intelephense-server

    "intelephense-ls":
    {
      "command": ["node", "/usr/local/lib/node_modules/intelephense-server/lib/server.js", "--stdio"],
      "scopes": ["source.php", "embedding.php"],
      "syntaxes": ["Packages/PHP/PHP.sublime-syntax"],
      "languageId": "php",
      "initializationOptions": {
        "storagePath": "/tmp/intelephense"
      }
    },

I'll close this issue for now but link it in the documentation for PHP support.

All 28 comments

Thanks for the pointers, perhaps this will inspire someone to try to get them working in LSP!

Both of these projects appear to only contain support for Visual Studio Code - intelephense is a library and Crane uses Node IPC (although easy to add tcp/stdio support). Do you know if there are stdio or tcp - based servers available around them? (eg. vim/emacs users may need standalone servers as well)

If not, unfortunately these projects cannot have any direct utility to LSP users in their current state, so it would be misleading to even advertise them in the documentation.

I will ask in their issue trackers, I could not find how to run them standalone. I'll reply here once I find the answer.

This was bmewburn's answer (from Intelephense):

Thanks for your interest, I'd like to see it used in other editors. The current limitation is that this is a lib that needs to be wrapped in a server layer that handles the language server protocol. I use this for vscode - https://github.com/Microsoft/vscode-languageserver-node/tree/master/server. I'm not sure how vscode specific it is, I suspect very little despite the name. See https://github.com/bmewburn/vscode-intelephense/tree/master/server for the vscode intelephense server that uses the above package. Theres some package.json compile and watch scripts specific to vscode extensions but apart from that this may even be able to be used as is if you have a client that can spawn the server and make lsp calls to it.

For what I saw, the server users a node's IPC stream, but it appears it accept any io stream, so we may be able to create a server using tcp I suppose.

we may be able to create a server using tcp I suppose.

I encourage you (as a PHP developer) to give this a try, see https://github.com/tomv564/lsp-tsserver/blob/a8548e183a69c048799e560819a3b7b1f08661d7/server/src/server.ts#L17 for an example of how to configure vscode-languageserver-node for stdio.
Crane also uses vscode-languageserver-node.

I hope it's okay if I close this issue for now, but please (re) open when we can document with a working example!

Thanks! I'll give it a try

Hello! I had some luck. Here's what I did:
1) Ugraded to node 9 (not sure if really needed)
2) cloned https://github.com/bmewburn/vscode-intelephense
3) Modified the code as suggested, just had to import the right classes:

@@ -11,13 +11,14 @@ import {
        CompletionItem, CompletionItemKind, RequestType, TextDocumentItem,
        PublishDiagnosticsParams, SignatureHelp, DidChangeConfigurationParams,
        Position, TextEdit, Disposable, DocumentRangeFormattingRequest,
-       DocumentFormattingRequest, DocumentSelector, TextDocumentIdentifier
+       DocumentFormattingRequest, DocumentSelector, TextDocumentIdentifier,
+    StreamMessageReader, StreamMessageWriter
 } from 'vscode-languageserver';

 import { Intelephense, IntelephenseConfig, InitialisationOptions, LanguageRange } from 'intelephense';

 // Create a connection for the server. The connection uses Node's IPC as a transport
-let connection: IConnection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process));
+let connection: IConnection = createConnection(new StreamMessageReader(process.stdin), new StreamMessageWriter(process.stdout));
 let initialisedAt: [number, number];

 const languageId = 'php';

4) run npm install to load dependencies
5) compile the typescript file (tsc server/src/server.ts)
6) added this configuration to sublime LSP settings:

{
    "clients": {
        "phpls": {
            "command": [
                "node",
                "~/www/vscode-intelephense/server/src/server.js"
            ],
            "enabled": true,
            "initializationOptions": {
                "nodePath": "/usr/bin/node"
            },
            "languageId": "php",
            "scopes": [
                "source.php",
                "embedding.php"
            ],
            "syntaxes": [
                "Packages/PHP/PHP.sublime-syntax"
            ]
        }
    },
    "log_debug": true,
    "log_payloads": true,
    "log_stderr": true,
    "only_show_lsp_completions": true
}

The last options are just for testing. I have mixed results. The server appears to be running fine, and there are moments when it works, but I have some errors. Here's the log:

Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP:      {'isIncomplete': False, 'items': [{'kind': 6, 'label': '$this', 'documentation': 'Refers to the current object', 'detail': 'M_customreport_builder_ExpressionBase'}]}
LSP:  --> textDocument/didChange
LSP:  --> textDocument/completion
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP:      {'isIncomplete': False, 'items': [{'kind': 10, 'label': 'calculateSubTotal', 'detail': ''}, {'kind': 10, 'label': 'options', 'detail': ''}, {'command': {'command': 'editor.action.triggerParameterHints', 'title': 'Trigger Parameter Hints'}, 'sortText': 'zzz', 'insertText': '__construct($0)', 'kind': 2, 'label': '__construct', 'insertTextFormat': 2, 'detail': '__construct($name, $alias, $label, M_customreport_builder_ExpressionCollection $links, $calculateSubTotal, M_customreport_json_OptionList $options, $id)'}, {'kind': 2, 'sortText': 'isOrderable', 'label': 'isOrderable', 'insertText': 'isOrderable()', 'detail': 'isOrderable()'}, {'kind': 2, 'sortText': 'getClone', 'label': 'getClone', 'insertText': 'getClone()', 'detail': 'getClone()'}, {'kind': 2, 'sortText': 'toJsonValue', 'label': 'toJsonValue', 'insertText': 'toJsonValue()', 'detail': 'toJsonValue()'}, {'command': {'command': 'editor.action.triggerParameterHints', 'title': 'Trigger Parameter Hints'}, 'sortText': 'setCalculateSubtotal', 'insertText': 'setCalculateSubtotal($0)', 'kind': 2, 'label': 'setCalculateSubtotal', 'insertTextFormat': 2, 'detail': 'setCalculateSubtotal($calculateSubTotal)'}, {'kind': 2, 'sortText': 'getCalculateSubTotal', 'label': 'getCalculateSubTotal', 'insertText': 'getCalculateSubTotal()', 'detail': 'getCalculateSubTotal()'}, {'kind': 2, 'sortText': 'removeLinks', 'label': 'removeLinks', 'insertText': 'removeLinks()', 'detail': 'removeLinks()'}, {'command': {'command': 'editor.action.triggerParameterHints', 'title': 'Trigger Parameter Hints'}, 'sortText': 'setOptions', 'insertText': 'setOptions($0)', 'kind': 2, 'label': 'setOptions', 'insertTextFormat': 2, 'detail': 'setOptions(M_customreport_json_OptionList $options)'}, {'command': {'command': 'editor.action.triggerParameterHints', 'title': 'Trigger Parameter Hints'}, 'sortText': 'addOption', 'insertText': 'addOption($0)', 'kind': 2, 'label': 'addOption', 'insertTextFormat': 2, 'detail': 'addOption($name, $value)'}, {'command': {'command': 'editor.action.triggerParameterHints', 'title': 'Trigger Parameter Hints'}, 'sortText': 'getOption', 'insertText': 'getOption($0)', 'kind': 2, 'label': 'getOption', 'insertTextFormat': 2, 'detail': 'getOption($name)'}, {'kind': 2, 'sortText': 'getOptions', 'label': 'getOptions', 'insertText': 'getOptions()', 'detail': 'getOptions()'}, {'kind': 2, 'sortText': 'zzz', 'label': '__clone', 'insertText': '__clone()', 'detail': '__clone()'}]}
LSP:  --> textDocument/didChange
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP:  --> textDocument/documentHighlight
LSP:      [{'kind': 2, 'range': {'start': {'line': 8, 'character': 12}, 'end': {'line': 8, 'character': 20}}}, {'kind': 2, 'range': {'start': {'line': 17, 'character': 15}, 'end': {'line': 17, 'character': 22}}}, {'kind': 2, 'range': {'start': {'line': 114, 'character': 15}, 'end': {'line': 114, 'character': 22}}}, {'kind': 2, 'range': {'start': {'line': 118, 'character': 20}, 'end': {'line': 118, 'character': 27}}}, {'kind': 2, 'range': {'start': {'line': 121, 'character': 15}, 'end': {'line': 121, 'character': 22}}}, {'kind': 2, 'range': {'start': {'line': 125, 'character': 22}, 'end': {'line': 125, 'character': 29}}}, {'kind': 2, 'range': {'start': {'line': 129, 'character': 22}, 'end': {'line': 129, 'character': 29}}}]
LSP:  --> textDocument/didChange
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP:  --> textDocument/didChange
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP:  --> textDocument/didClose
LSP:  --> textDocument/didChange
LSP:  --> textDocument/completion
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP:      {'isIncomplete': False, 'items': []}
LSP:  --> textDocument/didChange
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP:  --> textDocument/didChange
LSP:  --> textDocument/completion
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP:      {'isIncomplete': False, 'items': []}
LSP:  --> textDocument/didChange
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP:  --> textDocument/documentHighlight
LSP:      []
LSP:  --> textDocument/didChange

I constantly get the "cannot read property 'start' of undefined, not sure whose error is it. I'm just trying to get an autocomplete with a public method from my parent class, but it's not getting it.

Any ideas?

That was quick! I can help with a closer look tomorrow but my gut feeling
says intelephense’s Didchange handler expects s richer description of text
changes than LSP provides.
On Sat, Jan 27, 2018 at 6:40 PM Jose Canciani notifications@github.com
wrote:

Hello! I had some luck. Here's what I did:

  1. Ugraded to node 9 (not sure if really needed)
  2. cloned https://github.com/bmewburn/vscode-intelephense
  3. Modified the code as suggested, just had to import the right
    classes:

@@ -11,13 +11,14 @@ import {
CompletionItem, CompletionItemKind, RequestType, TextDocumentItem,
PublishDiagnosticsParams, SignatureHelp, DidChangeConfigurationParams,
Position, TextEdit, Disposable, DocumentRangeFormattingRequest,- DocumentFormattingRequest, DocumentSelector, TextDocumentIdentifier+ DocumentFormattingRequest, DocumentSelector, TextDocumentIdentifier,+ StreamMessageReader, StreamMessageWriter
} from 'vscode-languageserver';

import { Intelephense, IntelephenseConfig, InitialisationOptions, LanguageRange } from 'intelephense';

// Create a connection for the server. The connection uses Node's IPC as a transport-let connection: IConnection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process));+let connection: IConnection = createConnection(new StreamMessageReader(process.stdin), new StreamMessageWriter(process.stdout));
let initialisedAt: [number, number];

const languageId = 'php';

  1. compile the typescript file (tsc server/src/server.ts)
  2. added this configuration to sublime LSP settings:

{
"clients": {
"phpls": {
"command": [
"node",
"~/www/vscode-intelephense/server/src/server.js"
],
"enabled": true,
"initializationOptions": {
"nodePath": "/usr/bin/node"
},
"languageId": "php",
"scopes": [
"source.php",
"embedding.php"
],
"syntaxes": [
"Packages/PHP/PHP.sublime-syntax"
]
}
},
"log_debug": true,
"log_payloads": true,
"log_stderr": true,
"only_show_lsp_completions": true
}

The last options are just for testing. I have mixed results. The server
appears to be running fine, and there are moments when it works, but I have
some errors. Here's the log:

Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP: {'isIncomplete': False, 'items': [{'kind': 6, 'label': '$this', 'documentation': 'Refers to the current object', 'detail': 'M_customreport_builder_ExpressionBase'}]}
LSP: --> textDocument/didChange
LSP: --> textDocument/completion
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP: {'isIncomplete': False, 'items': [{'kind': 10, 'label': 'calculateSubTotal', 'detail': ''}, {'kind': 10, 'label': 'options', 'detail': ''}, {'command': {'command': 'editor.action.triggerParameterHints', 'title': 'Trigger Parameter Hints'}, 'sortText': 'zzz', 'insertText': '__construct($0)', 'kind': 2, 'label': '__construct', 'insertTextFormat': 2, 'detail': '__construct($name, $alias, $label, M_customreport_builder_ExpressionCollection $links, $calculateSubTotal, M_customreport_json_OptionList $options, $id)'}, {'kind': 2, 'sortText': 'isOrderable', 'label': 'isOrderable', 'insertText': 'isOrderable()', 'detail': 'isOrderable()'}, {'kind': 2, 'sortText': 'getClone', 'label': 'getClone', 'insertText': 'getClone()', 'detail': 'getClone()'}, {'kind': 2, 'sortText': 'toJsonValue', 'label': 'toJsonValue', 'insertText': 'toJsonValue()', 'detail': 'toJsonValue()'}, {'command': {'command': 'editor.action.triggerParameterHints', 'title': 'Trigger Parameter Hints'}, 'sortText': 'setCalculateSubtotal', 'insertText': 'setCalculateSubtotal($0)', 'kind': 2, 'label': 'setCalculateSubtotal', 'insertTextFormat': 2, 'detail': 'setCalculateSubtotal($calculateSubTotal)'}, {'kind': 2, 'sortText': 'getCalculateSubTotal', 'label': 'getCalculateSubTotal', 'insertText': 'getCalculateSubTotal()', 'detail': 'getCalculateSubTotal()'}, {'kind': 2, 'sortText': 'removeLinks', 'label': 'removeLinks', 'insertText': 'removeLinks()', 'detail': 'removeLinks()'}, {'command': {'command': 'editor.action.triggerParameterHints', 'title': 'Trigger Parameter Hints'}, 'sortText': 'setOptions', 'insertText': 'setOptions($0)', 'kind': 2, 'label': 'setOptions', 'insertTextFormat': 2, 'detail': 'setOptions(M_customreport_json_OptionList $options)'}, {'command': {'command': 'editor.action.triggerParameterHints', 'title': 'Trigger Parameter Hints'}, 'sortText': 'addOption', 'insertText': 'addOption($0)', 'kind': 2, 'label': 'addOption', 'insertTextFormat': 2, 'detail': 'addOption($name, $value)'}, {'command': {'command': 'editor.action.triggerParameterHints', 'title': 'Trigger Parameter Hints'}, 'sortText': 'getOption', 'insertText': 'getOption($0)', 'kind': 2, 'label': 'getOption', 'insertTextFormat': 2, 'detail': 'getOption($name)'}, {'kind': 2, 'sortText': 'getOptions', 'label': 'getOptions', 'insertText': 'getOptions()', 'detail': 'getOptions()'}, {'kind': 2, 'sortText': 'zzz', 'label': '__clone', 'insertText': '__clone()', 'detail': '__clone()'}]}
LSP: --> textDocument/didChange
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP: --> textDocument/documentHighlight
LSP: [{'kind': 2, 'range': {'start': {'line': 8, 'character': 12}, 'end': {'line': 8, 'character': 20}}}, {'kind': 2, 'range': {'start': {'line': 17, 'character': 15}, 'end': {'line': 17, 'character': 22}}}, {'kind': 2, 'range': {'start': {'line': 114, 'character': 15}, 'end': {'line': 114, 'character': 22}}}, {'kind': 2, 'range': {'start': {'line': 118, 'character': 20}, 'end': {'line': 118, 'character': 27}}}, {'kind': 2, 'range': {'start': {'line': 121, 'character': 15}, 'end': {'line': 121, 'character': 22}}}, {'kind': 2, 'range': {'start': {'line': 125, 'character': 22}, 'end': {'line': 125, 'character': 29}}}, {'kind': 2, 'range': {'start': {'line': 129, 'character': 22}, 'end': {'line': 129, 'character': 29}}}]
LSP: --> textDocument/didChange
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP: --> textDocument/didChange
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP: --> textDocument/didClose
LSP: --> textDocument/didChange
LSP: --> textDocument/completion
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP: {'isIncomplete': False, 'items': []}
LSP: --> textDocument/didChange
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP: --> textDocument/didChange
LSP: --> textDocument/completion
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP: {'isIncomplete': False, 'items': []}
LSP: --> textDocument/didChange
Notification handler 'textDocument/didChange' failed with message: Cannot read property 'start' of undefined:
LSP: --> textDocument/documentHighlight
LSP: []
LSP: --> textDocument/didChange

I constantly get the "cannot read property 'start' of undefined, not sure
whose error is it. I'm just trying to get an autocomplete with a public
method from my parent class, but it's not getting it.

Any ideas?

—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/tomv564/LSP/issues/259#issuecomment-361000909, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AEMTOB9JJiN6xFj5iU6qMWdn-hwr3TcAks5tO1-EgaJpZM4Rs7oo
.

Great, thanks! Let me know if I can do anything to help.

Hello, looks like this is an intelephense issue. I wasn't following the spec here which allows undefined range. Fixed in master.

wow, that was quick too. I'll keep playing with it tomorrow and let you know.

@bmewburn Great, now there's no errors I can see. Question: I opened a file and triggered an autocomplete on $this->, expecting to see the parent classes's public and protected methods. I could not see them, but after I opened and close the parent class, they started appearing.
Is this because the indexing was not complete? If that's the case, some questions/suggestions:

1) It would be nice that if the client asks for something in a file, the server could find class symbols in that file and try to index them too before replying (for what I read, LSP is supposed to work even during indexing). It seems it is only parsing the current file. I suppose it could be problematic if there are a lot of classes and the tree is big. Maybe there could be a recursion limit or something like that so it doesn't hang forever. Anyway, treat this as a suggestion only :)

2) Is there a way we can inform the client the indexing status? It would be nice to know at what point we are (not sure if that's already in the SPEC). I've noticed that in vscode there's a status message telling you the progress.

3) For easy configuration, we would need to create a new project that can be installed with npm. Something like "php-intellephense-server-io" (io for the stdin/stdout version, but I guess the listen method could be sent via a parameter). Today the server is in a folder inside your project but the rest of the code is not really needed, as it's added by npm install automatically on build. Is that something you'll be interested on doing? To avoid code duplication, you can add the server as a submodule in your main project.

This is looking great guys!!

Jose

Nice work, @josecanciani !

  1. There is some client side code that handles indexing in vscode. It uses the vscode api to get a list of workspace files -- workspace.findFiles -- and then sends the documents to the server. So an implementation of this function and the indexing logic would need to be written into the server to work with sublime LSP.
  2. This notification is from the client but could be moved to the server with above.
  3. I'm interested in forking https://github.com/bmewburn/vscode-intelephense/tree/master/server so the server can be used with any client and creating an npm package. I think the connection class params are optional and it will default to whatever cmd line flags are passed. There's a couple of things blocking me from working on the issues in 1 & 2 for now -- time and some of that indexing logic above will be changing soon with the next release of intelephense.

That's great! When you have the server as a standalone package, can you reply back here so that @tomv564 can update the documentation?

I tried it a bit more and I notice that static methods (ClassName::method) are not being suggested, and also that new ClassName or directly start typing a class name is not suggesting anything. I don't know if that's expected (haven't used vscode enough to compare), or if there's something the client needs to inform when triggering the auto complete.

Jose

I forked to https://github.com/bmewburn/intelephense-server and npm published [email protected] . I've removed the IPC params and will continue working on it when I have the time.

For documentation's sake, here is a configuration that allows a successful startup on OS-X after npm install -g intelephense-server

    "intelephense-ls":
    {
      "command": ["node", "/usr/local/lib/node_modules/intelephense-server/lib/server.js", "--stdio"],
      "scopes": ["source.php", "embedding.php"],
      "syntaxes": ["Packages/PHP/PHP.sublime-syntax"],
      "languageId": "php",
      "initializationOptions": {
        "storagePath": "/tmp/intelephense"
      }
    },

I'll close this issue for now but link it in the documentation for PHP support.

Thank you both! I'll switch to the new server.
@bmewburn, great work, I hope you can keep moving features to the server as possible :) Will the server always use the last version automatically, right?
Thanks you guys again.
Jose

@josecanciani the vscode intelephense client now depends on intelephense-server so there will be new versions published periodically. You'll have to npm upgrade -g intelephense-server to get new versions.

Does anyone have a way to index the full project in Sublime with intelephense-server? If I understand it correctly, the intelephense-server doesn't index a whole directory - it expects to be passed each file post-initiation, which is what the VS Code plugin does?

FYI I totally hacked in index-project support in https://github.com/tomv564/LSP/compare/master...joehoyle:index-project

Nice, is that really what the VS Code plugin does?
Indexing to get all workspace symbols etc. feels like a job for the server.

If you are expecting the client to open and return syntax errors for all files when opening a workspace, this was never in the VS Code / LSP model (as it will choke servers on large projects)

I would like to know that is there a way to make LSP's autocompletion (function parameters hinting) work without openning the definition file every time when I start Sublime Text. Linting only on the current opened file is okay and reasonable for me. But it is really weird that I have to open the definition file manually before the autocompletion works.

But I am not sure should this be done by LSP or the server.

Nice, is that really what the VS Code plugin does?

Yeah, it's in https://github.com/bmewburn/vscode-intelephense/blob/master/src/extension.ts#L205. Also LSP doesn't dictate the server should do it, didOpen is (though maybe arguable) just to tell the LSP-server about the existence of a file, not literally that the file is open for the user, see https://microsoft.github.io/language-server-protocol/specification#textDocument_didOpen.

If you hadn't seen it, there;s usuful discussion in https://github.com/Microsoft/language-server-protocol/issues/54 on this, which appears to lean in the direction of the client passing all files with existing APIs

The plan is to move indexing to the server (bmewburn/intelephense-server#1) in an effort to make it simpler to integrate with any editor. It ended up client side to leverage the vscode find files API and I was of the opinion that the server should be workspace ignorant and work on what it receives from the client.

Thanks for chiming in!

@joehoyle: Following the code in https://github.com/bmewburn/vscode-intelephense/blob/cdd5f66f09e8ee26a2d69a0799ba761c90bc8d43/src/workspaceDiscovery.ts#L10-L11 , it looks like custom discoverSymbols and discoverReferences requests are used (didn't see any textDocument/didOpen notifications ).

@tomv564 ah yes you are right - I don't know if this repo makes use of discoverSymbols or discoverReferences, but the same applies, currently we'd need to tell the LSP server about each file, whether that's via didOpen or discoverSymbols. That would all be moot of course if bmewburn/intelephense-server#1 is completed, @bmewburn any clues on how high a priority that one is?

@joehoyle this one is high on the to do list and will be part of next release.

Just a quick question here as I am trying to make it work, do I need to do something in order to make intelephense-ls do an indexing?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

arsham picture arsham  Â·  3Comments

JonasPf picture JonasPf  Â·  6Comments

JeremyBois picture JeremyBois  Â·  6Comments

lsmenicucci picture lsmenicucci  Â·  3Comments

evandrocoan picture evandrocoan  Â·  7Comments